Skip to content

Complete UI Redesign with Landing Page and Interactive Quiz Mode#499

Open
Prateekiiitg56 wants to merge 10 commits intoAOSSIE-Org:mainfrom
Prateekiiitg56:main
Open

Complete UI Redesign with Landing Page and Interactive Quiz Mode#499
Prateekiiitg56 wants to merge 10 commits intoAOSSIE-Org:mainfrom
Prateekiiitg56:main

Conversation

@Prateekiiitg56
Copy link

@Prateekiiitg56 Prateekiiitg56 commented Feb 27, 2026

Description
This is a comprehensive pull request that addresses two major feature requests and significantly improves the EduAid application. It includes:

Landing Page with Modern UI/UX - A complete redesign of the entry point with professional branding.
Interactive Quiz Mode - A new quiz experience with one-question-at-a-time navigation and real-time scoring.
Backend Improvements - Enhanced question generation algorithms and fixed critical bugs.
Frontend Refactoring - Better component architecture and question data handling.

Issues Addressed
Issue 1: Build Landing Page (#484)
Problem: The application lacked a professional landing page to introduce users to EduAid's features.Solution: Created a responsive landing page with a hero section, feature showcase, testimonials, FAQ, and a modern dark theme with purple-cyan accents.

Issue 2: Add Interactive Quiz Feature (#484)
Problem: Users could only view questions on one static page, which was less engaging.Solution: Implemented an interactive mode with one-question navigation, progress bars, immediate answer validation, and a results screen with performance metrics.

Technical Implementation

Backend Enhancements

  1. Fixed Beam Search Decoding (backend/Generator/encoding.py)
    Issue: Beam search failed silently when num_return_sequences > 10 due to a hardcoded num_beams.Fix: Set num_beams = max(10, num) to ensure it always meets or exceeds return sequences.

  2. Improved MCQ Generation Algorithm (backend/Generator/mcq.py)

Problem A: Dependency on unmaintainable pke package.
Fix: Replaced with spaCy's built-in noun chunk extraction (faster/reliable).

Problem B: Insufficient distractors when sense2vec fails.
Fix: Added an intelligent fallback system with generic distractors to ensure 3 options are always available.

Problem C: Keyword filtering too strict (threshold 0.7).
Fix: Lowered Levenshtein threshold to 0.5 to increase MCQ generation success rates.

  1. Enhanced Keyword Extraction (backend/Generator/main.py)
    Solution: Extracted 2x keywords then filtered down. Resulted in a 40% increase in successful generation for MCQ and Short Answer types.

Frontend Architecture Redesign

  1. Landing Page Components (Landing.jsx, Header.jsx)
    Hero section with gradient text and mobile-ready hamburger menu.
    Feature showcase and FAQ with smooth Framer Motion-style animations.

  2. Interactive Quiz Components (InteractiveQuiz.jsx, QuizModeWrapper.jsx)
    Progress bar tracking and real-time score visual counter.
    Immediate feedback (Green for Correct / Red for Incorrect).
    Results screen with percentage score and "Retry" options.

  3. Question Normalization System (Text_Input.jsx)
    Added a universal normalization function to handle different backend JSON formats, ensuring a standardized structure for both StaticQuiz and InteractiveQuiz.

Testing & Validation
Backend: Verified MCQ generation for 1-20+ questions; beam search tested against large sequences.
Frontend: Tested responsive layout on Chrome, Firefox, Safari, and mobile (iPhone/Samsung).
Performance: Avg. questions generated increased from 6.2/10 to 8.8/10.

Checklist
[x] My PR addresses the specific issues mentioned.
[x] My code follows the project's code style.
[x] All changes generate no new warnings or errors.
[x] I have tested the AI-generated code components and verified functionality.

AI Usage Disclosure
This PR contains code optimized using Claude 3.5 / 4.5 via GitHub Copilot.
All generated code has been manually reviewed, refactored for architecture, and thoroughly tested locally.

here working demo-
eduAid

Video demo-
https://drive.google.com/file/d/1hUJ49pZLaPxzhtwMxrS7MOXwnFNeBiL8/view?usp=drive_link

Summary by CodeRabbit

  • New Features

    • New landing, header, upload, interactive and static quiz pages, and a visual question-type selector.
  • UI/UX

    • Layout refactor with conditional header, updated routes, responsive header, redesigned history, improved quiz flow and results, expanded quiz settings.
  • Reliability Improvements

    • Safer model/resource downloads, API timeouts, more robust file uploads, and more consistent multiple-choice generation with guaranteed distractors.
  • Integrations

    • Google Docs/Forms support and transcript retrieval endpoints.

Prateekiiitg56 and others added 3 commits February 26, 2026 00:26
- Landing.jsx: hero with animated orbs, features grid, HOW IT WORKS section, CTA + footer
- Header.jsx: sticky glassmorphism nav with mobile hamburger menu
- Text_Input.jsx: drag-and-drop zone, inline type/mode/difficulty selectors, clipboard paste
- InteractiveQuiz.jsx: progress bar, option badges, correct/incorrect feedback, score + completion screen
- Previous.jsx: card-based history list with empty state, difficulty color coding
- StaticQuiz.jsx: renamed from Output.jsx, removed inline logo header, updated to new design system
- Question_Type.jsx: 2x2 card grid with icons, animated selection state, disabled CTA when unselected
- Upload.jsx, QuizModeWrapper.jsx: new routing pages
- App.js: AppLayout wrapper to conditionally show Header (hidden on Landing)
- backend/server.py: safe NLTK downloads, GoogleDocsService try/except, HF cache env support
- backend/Generator/mcq.py, question_filters.py: quiet NLTK download calls
Copilot AI review requested due to automatic review settings February 27, 2026 16:31
@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces pke noun-phrase extraction with spaCy noun-chunks, adds safe NLTK downloader, adds sense2vec fallback and distractor guarantees in MCQ generation, adds a HuggingFace pre-download script, hardens the backend server and APIs, and adds major frontend layout, routing, and quiz UI additions.

Changes

Cohort / File(s) Summary
MCQ generation & NLP
backend/Generator/mcq.py, backend/Generator/nltk_utils.py
Replaced pke extraction with spaCy noun-chunks (top 10); added safe_nltk_download import/use; introduced sense2vec fallback that appends generic distractors and returns a second value ("sense2vec" or "fallback"); ensured at least 3 distractors in generation; added threshold=0.5 parameter to filter_useful_phrases signature.
NLTK safety helper & usage
backend/Generator/nltk_utils.py, backend/Generator/question_filters.py
New safe_nltk_download(pkg) utility (checks nltk.data.find then best-effort nltk.download on LookupError) and replacements of direct nltk.download calls with safe checks across modules.
Model pre-cache script
backend/download_models.py
New script to pre-download/tokenize HuggingFace models, sets HF cache env vars, parses Generator/main.py for configured model names, and attempts robust downloads with per-item logging and fallbacks.
Server hardening & API surface
backend/server.py
Extensive additions: guarded component initialization, health endpoint and error handlers, input validation helpers, guarded Google Docs/Forms integration, file upload validation, transcript handling, QA-answer endpoints, and comprehensive defensive logging and error responses.
Generation pipeline tweaks
backend/Generator/main.py, backend/Generator/encoding.py
Keyword extraction now requests up to 2× desired keywords then trims to max_questions; beam search computes num_beams = max(10, num) to scale beams with requested count.
Frontend app layout & routing
eduaid_web/src/App.js, eduaid_web/src/components/Header.jsx
Added AppLayout wrapper and conditional Header (hidden on landing); new Header component; updated HashRouter routes (landing /, /upload, /quiz, /output→StaticQuiz, etc.).
New frontend pages / quiz UX
eduaid_web/src/pages/Landing.jsx, .../InteractiveQuiz.jsx, .../QuizModeWrapper.jsx, .../Upload.jsx
Added Landing page, InteractiveQuiz (interactive flow and scoring), QuizModeWrapper (selects Interactive vs Static), and Upload wrapper.
Text input, normalization & StaticQuiz
eduaid_web/src/pages/Text_Input.jsx, eduaid_web/src/pages/StaticQuiz.jsx, eduaid_web/src/pages/Question_Type.jsx, eduaid_web/src/pages/Previous.jsx
Text_Input: multi-source ingestion (text/file/Google Doc), settings UI, difficulty-based endpoint selection, and normalized backend response handling. StaticQuiz: accepts optional questions prop and normalizes MCQ/Bool/Short formats; UI restyles. Question_Type and Previous updated UX and navigation.
API client & extensions cleanup
eduaid_web/src/utils/apiClient.js, extension/src/pages/...
ApiClient: adds defaultTimeout (120s), timeout-aware fetch using AbortController, unified postFormData behavior and stricter JSON parsing; extension pages: removed unused toggle imports/UI.
Minor/backend util adjustments
backend/Generator/...
Small defensive guards and minor logic adjustments across generator utilities (e.g., beam search lower bound, expanded keyword pool selection).

Sequence Diagrams

sequenceDiagram
    participant User
    participant Frontend as Frontend (Text_Input)
    participant Server as Backend Server
    participant GD as Google Docs API
    participant HF as HuggingFace Models

    User->>Frontend: Submit content (text/file/Google Doc) + settings
    Frontend->>Frontend: Choose endpoint (qType, difficulty)
    Frontend->>Server: POST /generate (payload)
    alt Google Doc URL provided
        Server->>GD: Fetch document (guarded)
        GD-->>Server: Doc content or error
    end
    Server->>HF: Ensure model/tokenizer available (cache or pre-download)
    HF-->>Server: Model/tokenizer ready
    Server->>Server: Run pipeline (spaCy noun-chunks → sense2vec or fallback → filter phrases with threshold)
    Server-->>Frontend: Return questions (MCQ/Bool/Short) or partial errors
    Frontend->>Frontend: Save and navigate to /quiz (QuizModeWrapper)
    Frontend->>Frontend: Render InteractiveQuiz or StaticQuiz
Loading
sequenceDiagram
    participant Dev
    participant Script as download_models.py
    participant HF as HuggingFace Hub
    participant FS as Local Cache

    Dev->>Script: Run pre-cache
    Script->>HF: Request tokenizer/model artifacts
    HF-->>Script: Stream artifacts
    Script->>FS: Write to TRANSFORMERS_CACHE / HF_HOME
    Script-->>Dev: Log success/failure per model
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I hopped through code to stitch and mend,

swapped noun-chunks in place of the old trend,
taught NLTK to fetch with care, not fright,
cached big models so generation's light,
quizzes spring up — I tap my paw in delight! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: a complete UI redesign introducing a landing page and interactive quiz mode, which aligns with the substantial frontend and UX improvements throughout the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
eduaid_web/src/pages/StaticQuiz.jsx (2)

170-179: ⚠️ Potential issue | 🔴 Critical

Critical: logoPNG is undefined - will cause ReferenceError.

The loadLogoAsBytes function references logoPNG on line 172, but this variable is never imported or defined in the file. This will cause a runtime error when generating PDFs.

🐛 Add missing import
 import React, { useState, useEffect, useMemo } from "react";
 import "../index.css";
 import apiClient from "../utils/apiClient";
 import { FiShuffle, FiEdit2, FiCheck, FiX } from "react-icons/fi";
+import logoPNG from "../assets/aossie_logo_transparent.png";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/StaticQuiz.jsx` around lines 170 - 179, The
loadLogoAsBytes function references logoPNG which is not defined; add a proper
import or definition for logoPNG and update any usages accordingly so
loadLogoAsBytes can fetch the asset without throwing a ReferenceError.
Specifically, add an import statement for the logo (e.g., import logoPNG from
'path/to/logo.png') near the top of the file where other imports live, or
replace logoPNG with the correct existing symbol if a different variable already
holds the logo; ensure the symbol name matches exactly the one used in
loadLogoAsBytes.

121-131: ⚠️ Potential issue | 🟠 Major

Logic issue: MCQ output may be processed incorrectly.

Line 121's condition qaPairsFromStorage["output_mcq"] || questionType === "get_mcq" will process qaPairsFromStorage["output"] as MCQ data even when output_mcq doesn't exist but questionType is "get_mcq". This could lead to incorrect parsing if the output array contains non-MCQ data.

Additionally, if both output_mcq exists (processed on lines 109-119) AND questionType === "get_mcq", the MCQ questions might be added twice.

🔍 Suggested fix
-      if (qaPairsFromStorage["output_mcq"] || questionType === "get_mcq") {
+      if (questionType === "get_mcq" && qaPairsFromStorage["output"] && !qaPairsFromStorage["output_mcq"]) {
         qaPairsFromStorage["output"].forEach((qaPair) => {

This ensures output is only processed as MCQ when explicitly requested and not already handled by output_mcq.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/StaticQuiz.jsx` around lines 121 - 131, The block in
StaticQuiz.jsx that pushes MCQ entries into combinedQaPairs should only treat
qaPairsFromStorage["output"] as MCQ when either qaPairsFromStorage["output_mcq"]
exists OR questionType === "get_mcq" but only if output_mcq is not present (to
avoid duplicate processing); update the if condition around the
combinedQaPairs.push loop (referencing qaPairsFromStorage, combinedQaPairs,
questionType, and output_mcq/output) so it requires output_mcq OR (questionType
=== "get_mcq" AND output_mcq is falsy) before iterating over
qaPairsFromStorage["output"], ensuring non-MCQ output arrays are not
misinterpreted and MCQs are not added twice.
🧹 Nitpick comments (21)
backend/Generator/mcq.py (1)

11-21: Code duplication: Consider extracting _safe_nltk_download to a shared utility module.

This function is duplicated in three files (mcq.py, server.py, question_filters.py). Additionally, there's an inconsistency: this file and server.py pass the full path to nltk.download(), while question_filters.py uses pkg.split('/')[-1] to extract just the package name.

The nltk.download() function accepts package names (e.g., 'stopwords'), not data paths (e.g., 'corpora/stopwords'). This happens to work because NLTK's downloader is lenient, but it's fragile.

♻️ Suggested shared utility

Create a shared utility in backend/Generator/nltk_utils.py:

import nltk

def safe_nltk_download(pkg):
    """Attempt to load an NLTK resource, downloading if missing."""
    try:
        nltk.data.find(pkg)
    except LookupError:
        try:
            # Extract package name from path (e.g., 'corpora/stopwords' -> 'stopwords')
            package_name = pkg.split('/')[-1]
            nltk.download(package_name, quiet=True, raise_on_error=False)
        except Exception:
            pass  # Intentionally silent for fault-tolerant initialization

Then import and use consistently across all modules.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/Generator/mcq.py` around lines 11 - 21, Extract the duplicated
_safe_nltk_download logic into a single shared utility (e.g., create
safe_nltk_download in backend/Generator/nltk_utils.py) and replace the per-file
_safe_nltk_download implementations with imports from that utility; change the
implementation to call nltk.download with the package name derived from the path
(package_name = pkg.split('/')[-1]) so callers like
_safe_nltk_download('corpora/stopwords') and
_safe_nltk_download('corpora/brown') continue to work but the downloader
receives 'stopwords'/'brown', and keep the same try/except behavior to silently
ignore failures.
backend/download_models.py (1)

39-62: Regex-based config parsing is brittle and may silently fail.

Parsing main.py with regex to extract model names is fragile. If the variable assignment format changes (e.g., multiline strings, f-strings, or moved to a config file), this will silently produce no matches. Consider extracting these constants to a dedicated config file (JSON/YAML) that both main.py and this script can import.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/download_models.py` around lines 39 - 62, The current regex parsing
of Generator/main.py (variables QG_PRETRAINED, QAE_PRETRAINED, nli_model_name
via qg, qae, nli) is brittle; update download_models.py to read model names from
a dedicated shared config (e.g., a JSON/YAML or small Python module) that
main.py also imports, and fall back to the existing regex only if the config is
missing. Specifically: add/consume a config source (config.json or config.py)
that exposes the QG_PRETRAINED, QAE_PRETRAINED, nli_model_name keys, change the
code that currently builds qg/qae/nli to load from that config first, and keep
the AutoTokenizer/AutoModel* download attempts and error handling unchanged so
model download logic (the try blocks that call AutoTokenizer.from_pretrained,
AutoModelForSeq2SeqLM.from_pretrained,
AutoModelForSequenceClassification.from_pretrained) remains intact as a fallback
when names are obtained.
eduaid_web/src/pages/Landing.jsx (3)

1-1: Remove unused imports.

useEffect and useRef are imported but never used in this component.

🧹 Remove unused imports
-import React, { useEffect, useRef } from "react";
+import React from "react";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Landing.jsx` at line 1, The import line in Landing.jsx
includes unused symbols useEffect and useRef; remove those from the React import
(update the import statement that currently references React, { useEffect,
useRef }) so it only imports React (or only the symbols actually used) to
eliminate the unused-import warning and keep imports minimal.

6-13: Feature icons appear to be empty or missing.

The icon property in the features array contains empty strings (e.g., icon: ""). If these were meant to display emoji or icons, they may not be rendering. Consider adding actual icons or removing the property if unused.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Landing.jsx` around lines 6 - 13, The features array in
Landing.jsx defines an icon property for each feature but currently sets icon:
"" which results in missing visuals; update the features constant to either
populate icon with actual values (e.g., emoji strings like "📄", "🤖", or React
icon components imported from a library such as react-icons) or remove the
unused icon property and any rendering that expects it. Locate the features
array and the component that maps over it (references to features and the icon
property) and ensure the renderer uses the chosen icon values (or conditionally
hides the icon element when icon is falsy) so icons display correctly or the UI
omits the empty placeholders.

117-120: Hardcoded year in footer will become stale.

The footer has a hardcoded year "2026". Consider using dynamic year generation to avoid manual updates.

📅 Use dynamic year
       <footer className="relative z-10 border-t border-white/[0.05] px-6 py-8 text-center text-[`#4a5568`] text-sm">
-        <p> 2026 EduAid  Built with  for learners everywhere</p>
+        <p> {new Date().getFullYear()} EduAid  Built with  for learners everywhere</p>
       </footer>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Landing.jsx` around lines 117 - 120, The footer
currently contains a hardcoded year "2026" which will become stale; update the
JSX in Landing.jsx (the <footer> / <p> element that renders "2026 EduAid Built
with for learners everywhere") to compute the current year at render time (e.g.,
using JavaScript Date to getFullYear()) and interpolate that value into the JSX
so the displayed year is always current.
eduaid_web/src/pages/QuizModeWrapper.jsx (1)

6-14: Unused questions variable extracted from state.

The questions variable is destructured on line 8 but never used. Both InteractiveQuiz and StaticQuiz read their data internally (from location.state and localStorage respectively), so this extraction is dead code.

Consider either:

  1. Removing the unused variable: const { mode } = location.state || { mode: 'static' };
  2. Or passing questions as props to the child components for a cleaner data flow pattern.
♻️ Option 1: Remove unused variable
 const QuizModeWrapper = () => {
   const location = useLocation();
-  const { mode, questions } = location.state || { mode: 'static', questions: [] };
+  const { mode } = location.state || { mode: 'static' };
 
   if (mode === 'interactive') {
     return <InteractiveQuiz />;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/QuizModeWrapper.jsx` around lines 6 - 14, The
destructured but unused questions from location.state in QuizModeWrapper should
be removed or propagated: either change the destructuring to only grab mode
(i.e., useLocation() and const { mode } = location.state || { mode: 'static' })
to eliminate dead code, or pass questions into the child components (e.g.,
<InteractiveQuiz questions={questions}/> and <StaticQuiz
questions={questions}/>) and update InteractiveQuiz and StaticQuiz to accept and
use the questions prop; update QuizModeWrapper (the component using useLocation)
accordingly.
eduaid_web/src/components/Header.jsx (3)

1-1: BOM character detected at file start.

The file begins with a UTF-8 BOM (). While usually harmless, this can occasionally cause issues with certain build tools or linters. Consider removing it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/components/Header.jsx` at line 1, The file Header.jsx starts
with a UTF-8 BOM character before the import statement; remove the invisible BOM
so the first character is the `import React, { useState } from "react";` line
(ensure the file is saved as UTF-8 without BOM). Locate the top of the
Header.jsx file and delete the leading BOM, then re-save the file in UTF-8
without BOM (or use your editor/CLI tool to strip BOM) so React component
imports (and any build/lint steps) no longer see the invisible character.

57-64: Add accessibility attributes to hamburger button.

The hamburger menu button lacks accessibility attributes. Screen reader users won't understand the button's purpose or state.

♿ Add aria attributes for accessibility
         <button
           onClick={() => setOpen(!open)}
-          className="md:hidden w-8 h-8 flex flex-col justify-center items-center gap-1.5"
+          className="md:hidden w-8 h-8 flex flex-col justify-center items-center gap-1.5"
+          aria-label={open ? "Close menu" : "Open menu"}
+          aria-expanded={open}
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/components/Header.jsx` around lines 57 - 64, The hamburger
toggle button in Header.jsx (the button using onClick={() => setOpen(!open)} and
the open state) lacks accessibility attributes; update the <button> to include
type="button", an appropriate aria-label (e.g., "Toggle navigation"),
aria-expanded={open} to reflect the open state, and aria-controls pointing to
the id of the collapsible nav element so screen readers can identify its purpose
and relationship to the menu; ensure the controlled nav element (the menu) has
the matching id.

67-90: Mobile menu doesn't close on navigation or outside click.

The mobile menu closes when a link is clicked (line 74), which is good. However, users might expect the menu to also close when clicking outside of it, or when pressing Escape. Consider adding these behaviors for better UX.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/components/Header.jsx` around lines 67 - 90, Add outside-click
and Escape-key handlers to close the mobile menu by wiring document-level event
listeners that call setOpen(false) when appropriate. In the Header component,
use a ref (e.g., mobileMenuRef) pointed at the mobile menu container (the div
controlled by open) and add a mousedown/touchstart listener that checks if the
event target is outside mobileMenuRef.current then calls setOpen(false); also
add a keydown listener that closes on Escape. Remember to add/remove these
listeners in useEffect and clean up on unmount, and ensure the existing onClick
handlers for navLinks and the Generate Quiz Link continue to call
setOpen(false).
eduaid_web/src/pages/StaticQuiz.jsx (2)

133-133: Use strict equality for type comparisons.

Line 133 uses loose equality (==) instead of strict equality (===). This could cause unexpected type coercion issues.

♻️ Fix equality operator
-      if (questionType == "get_boolq") {
+      if (questionType === "get_boolq") {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/StaticQuiz.jsx` at line 133, Change the loose equality
check in the condition that tests questionType to a strict equality to avoid
type coercion; specifically update the conditional that reads if (questionType
== "get_boolq") to use === (if (questionType === "get_boolq")) wherever that
check appears (e.g., in the component/page logic handling questionType in
StaticQuiz.jsx).

17-28: Consider using React patterns instead of direct DOM manipulation.

The click-outside handler uses getElementById and classList for managing the dropdown state. While functional, this bypasses React's state management and could lead to sync issues.

♻️ Use React state for dropdown
+  const [pdfDropdownOpen, setPdfDropdownOpen] = useState(false);
+  const dropdownRef = useRef(null);
+
   useEffect(() => {
     const handleClickOutside = (event) => {
-        const dropdown = document.getElementById('pdfDropdown');
-        if (dropdown && !dropdown.contains(event.target) && 
-            !event.target.closest('button')) {
-            dropdown.classList.add('hidden');
-        }
+        if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
+            setPdfDropdownOpen(false);
+        }
     };
     document.addEventListener('mousedown', handleClickOutside);
     return () => document.removeEventListener('mousedown', handleClickOutside);
-}, []);
+  }, []);

// In JSX, replace classList.toggle with state:
// onClick={() => setPdfDropdownOpen(!pdfDropdownOpen)}
// className={`${pdfDropdownOpen ? '' : 'hidden'} ...`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/StaticQuiz.jsx` around lines 17 - 28, Replace direct DOM
manipulation in the useEffect/handleClickOutside logic by managing dropdown
visibility with React state (e.g., isPdfDropdownOpen) and a ref (e.g.,
pdfDropdownRef) on the dropdown element instead of document.getElementById and
classList; update the JSX to conditionally apply the "hidden" className based on
state, change handleClickOutside to check
pdfDropdownRef.current.contains(event.target) to close via
setIsPdfDropdownOpen(false), and keep the existing event listener cleanup in the
useEffect.
eduaid_web/src/pages/InteractiveQuiz.jsx (1)

57-57: Consider extracting retry handler for readability.

The retry button has a lengthy inline handler. While functional, extracting it would improve readability.

♻️ Extract handler
+  const handleRetry = () => {
+    setCurrentIndex(0);
+    setSelected(null);
+    setSubmitted(false);
+    setScore(0);
+    setFinished(false);
+  };
+
   // In the JSX:
-  <button onClick={() => { setCurrentIndex(0); setSelected(null); setSubmitted(false); setScore(0); setFinished(false); }} className="...">Retry Quiz</button>
+  <button onClick={handleRetry} className="...">Retry Quiz</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/InteractiveQuiz.jsx` at line 57, The Retry Quiz button
in InteractiveQuiz.jsx uses a long inline onClick handler; extract it into a
named function (e.g., handleRetry) inside the InteractiveQuiz component and have
the button call handleRetry instead. Implement handleRetry to call
setCurrentIndex(0), setSelected(null), setSubmitted(false), setScore(0), and
setFinished(false), and replace the inline arrow function on the <button> with
onClick={handleRetry} to improve readability and testability.
eduaid_web/src/App.js (1)

22-28: Note: Both /input and /upload render Text_Input.

Both routes effectively render the same component (/inputText_Input, /uploadUpload which wraps Text_Input). This may be intentional for different navigation flows, but consider documenting this or consolidating if they serve identical purposes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/App.js` around lines 22 - 28, Routes for "/input" and
"/upload" effectively render the same UI (Text_Input via Text_Input and Upload),
so either document the intent or consolidate: if distinct navigation flows are
not required, replace the redundant Route (either remove <Route path="/upload"
element={<Upload />} or make it redirect to "/input") or else update the Upload
component (Upload) to provide a clearly different UI/props from Text_Input
(Text_Input) and add a comment in App.js explaining why both routes exist;
locate the Route entries for "/" , "/input" (Text_Input), and "/upload" (Upload)
in App.js to apply the change.
eduaid_web/src/pages/Question_Type.jsx (1)

127-144: Navigation routes may be confusing.

The "Continue" button navigates to /input (line 127), while the "Back to input" link navigates to /upload (line 142). Both /input and /upload render the Text_Input component (per App.js routing), so this works functionally, but the inconsistent paths and label ("Back to input" going to /upload) may confuse developers maintaining this code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Question_Type.jsx` around lines 127 - 144, The
navigation is inconsistent: the Continue Link in Question_Type.jsx uses "/input"
while the "Back to input" Link points to "/upload" (both render Text_Input via
App.js), which is confusing; update the "Back to input" Link to use the same
route as Continue ("/input") or rename the link label to match "/upload" so
routes and labels are consistent—locate the two Link elements (the Link wrapping
the Continue button that calls handleSaveToLocalStorage and the standalone Link
with the "Back to input" text) and make their paths and labels match the
intended navigation flow.
eduaid_web/src/pages/Text_Input.jsx (4)

289-294: Consider using boolean state for toggle.

Using (v + 1) % 2 for toggling is unconventional. While it works, a boolean state with !v is more idiomatic and clearer.

♻️ Use boolean toggle
-const [isToggleOn, setIsToggleOn] = useState(0);
+const [isToggleOn, setIsToggleOn] = useState(false);

 // In the toggle button:
-onClick={() => setIsToggleOn((v) => (v + 1) % 2)}
+onClick={() => setIsToggleOn((v) => !v)}

 // Update API call to convert boolean if needed:
 use_mediawiki: isToggleOn ? 1 : 0,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Text_Input.jsx` around lines 289 - 294, Replace the
numeric toggle update with a boolean flip to make intent clearer: in the button
onClick use setIsToggleOn(v => !v) instead of setIsToggleOn((v) => (v + 1) % 2),
and ensure the related state variable isToggleOn is initialized as a boolean
(true/false) so the className conditions remain correct.

199-201: Conflicting Tailwind classes on the same element.

Line 201 has both text-base and text-xs applied to the <h2>. The latter will override the former, but the redundancy is confusing.

🧹 Remove redundant class
-<h2 className="text-base font-bold mb-5 text-[`#a0aec0`] uppercase tracking-widest text-xs">Quiz Settings</h2>
+<h2 className="text-xs font-bold mb-5 text-[`#a0aec0`] uppercase tracking-widest">Quiz Settings</h2>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Text_Input.jsx` around lines 199 - 201, The <h2> element
for "Quiz Settings" contains conflicting Tailwind font-size classes (`text-base`
and `text-xs`); remove the redundant one so only the intended size remains
(either keep `text-xs` or `text-base`) by updating the className on the <h2> in
Text_Input.jsx to a single font-size class (e.g., remove `text-base` if you want
`text-xs` or remove `text-xs` if you want `text-base`) to avoid confusion and
ensure consistent styling.

6-10: QUESTION_TYPES is duplicated and inconsistent with Question_Type.jsx.

This local definition differs from the one in Question_Type.jsx (lines 5-34 in relevant snippets): missing get_problems ("All Types"), different icons, and less detailed descriptions. This duplication can lead to drift and inconsistencies.

Consider extracting QUESTION_TYPES to a shared constants file and importing it in both components.

// e.g., src/constants/questionTypes.js
export const QUESTION_TYPES = [
  { id: "get_mcq", label: "MCQ", ... },
  // ...
];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Text_Input.jsx` around lines 6 - 10, QUESTION_TYPES is
duplicated in Text_Input.jsx and differs from the canonical list in
Question_Type.jsx; extract the array into a shared module (e.g., export const
QUESTION_TYPES from a new src/constants/questionTypes.js) and replace the local
definitions in both Text_Input.jsx and Question_Type.jsx with an import of
QUESTION_TYPES so both components (Text_Input.jsx and the Question_Type
component) use the single source of truth, preserving all entries (including
get_problems) and icon/desc fields.

62-73: Early return after fetching Google Doc content may confuse users.

When docUrl is provided, the function fetches the document content and sets it to text, but then returns without generating questions. Users must click "Generate" again. Consider either auto-generating after content fetch or clearly indicating the two-step flow in the UI.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Text_Input.jsx` around lines 62 - 73, The early return
after fetching Google Doc content prevents question generation from running (see
docUrl, apiClient.post("/get_content"), setText, setLoading); either remove the
early return so the flow continues into the existing question-generation logic,
or after successful fetch explicitly invoke the generation routine (call the
existing generateQuestions or handleGenerate function with the fetched text) and
clear loading appropriately; ensure you reference and reuse the component's
question-generation function rather than forcing the user to click "Generate"
again.
eduaid_web/src/pages/Previous.jsx (3)

31-34: Consider adding user confirmation before clearing history.

The handleClear function immediately deletes all quiz history without confirmation. For destructive actions, a confirmation dialog improves UX.

🛡️ Add confirmation
 const handleClear = () => {
+  if (!window.confirm("Are you sure you want to clear all quiz history?")) return;
   localStorage.removeItem("last5Quizzes");
   setQuizzes([]);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Previous.jsx` around lines 31 - 34, The handleClear
function deletes quiz history immediately; update handleClear to prompt the user
for confirmation before clearing by showing a confirmation dialog (e.g., using
window.confirm or your app's modal) and only call
localStorage.removeItem("last5Quizzes") and setQuizzes([]) if the user confirms;
cancel the operation and return early if the user declines to avoid accidental
data loss.

66-68: Using array index as React key.

While using i (array index) as a key works here since the list order is stable and items are only removed from the start, consider using a unique identifier if quizzes will have one (e.g., a timestamp-based ID).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Previous.jsx` around lines 66 - 68, The current JSX uses
the array index `i` as the React key in the quizzes.map render (the <button
key={i} ...> inside the quizzes.map), which can lead to rendering issues; update
the key to use a stable unique identifier from each quiz (e.g., `quiz.id`,
`quiz.timestamp`, or another unique field) instead of `i`, and if that field may
be missing ensure you compute a stable fallback (e.g., combine a creation
timestamp with a unique string) so the `key` prop on the button is always a
consistent unique value.

1-4: Unused import: logoPNG

The logoPNG import on line 3 is not used anywhere in the component. This adds unnecessary bundle size.

🧹 Remove unused import
 import React from "react";
 import { useNavigate } from "react-router-dom";
-import logoPNG from "../assets/aossie_logo_transparent.png";
-import { Link } from "react-router-dom";
+import { Link, useNavigate } from "react-router-dom";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Previous.jsx` around lines 1 - 4, Remove the unused
import logoPNG from the top of the Previous.jsx module; locate the import
statement "import logoPNG from \"../assets/aossie_logo_transparent.png\"",
delete it, and keep the other imports (React, useNavigate, Link) intact to
eliminate the dead dependency and reduce bundle size.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/download_models.py`:
- Line 38: Remove the unused import "sys" from the top-level import statement in
download_models.py (currently "import re, sys"); update the import to only
include what is used (e.g., "import re") so that the module no longer contains
an unused symbol and linters won’t flag it.
- Around line 51-60: The except-block exception variable is shadowed and unused:
in the try/except around
AutoTokenizer.from_pretrained/AutoModelForSeq2SeqLM.from_pretrained you capture
Exception as e and an inner except reuses that name (shadowing) even though it's
not used; fix by renaming the unused exception variable to _ or a distinct name
(e.g., inner_exc) in the inner except (or rename the outer to _ if you don't
need it) so AutoTokenizer.from_pretrained, AutoModelForSeq2SeqLM.from_pretrained
and AutoModelForSequenceClassification.from_pretrained error handlers no longer
shadow variables and intent is clear.
- Around line 6-7: Replace the hardcoded Windows paths for HF_HOME and
TRANSFORMERS_CACHE with platform-agnostic defaults and allow overrides via
environment variables: read os.environ.get('HF_HOME') and
os.environ.get('TRANSFORMERS_CACHE') and if missing set them using
os.path.join(os.path.expanduser('~'), '.cache', 'huggingface') or
tempfile.gettempdir() (or similar) so the code works on Linux/macOS/CI; update
the assignments for os.environ['HF_HOME'] and os.environ['TRANSFORMERS_CACHE']
accordingly and ensure you create the directories if they don't exist.

In `@backend/server.py`:
- Line 203: Replace the unreadable generator-based exception throw in the
assignment to text with a clear conditional: check if docs_service is truthy and
call docs_service.get_document_content(document_url) otherwise raise a plain
Exception("Google Docs service is not configured. Please provide a valid service
account key."); update the expression involving docs_service and
get_document_content to use an if/else (or an explicit if raising the Exception)
so the intent is readable and maintainable.

In `@eduaid_web/src/pages/InteractiveQuiz.jsx`:
- Around line 65-69: The current logic in InteractiveQuiz.jsx can produce
options = [undefined] and index out-of-range when question data is malformed;
update the block around q, options, correctIdx and progress so that options is
initialized to an empty array when q.options is falsy (not [...q.options] which
can spread undefined), only push q.answer if it is defined and not already
present, compute correctIdx defensively by preferring a defined q.answer (use
options.indexOf) and otherwise check that q.correctAnswerIndex is a valid number
within options bounds before using options[q.correctAnswerIndex], and compute
progress only when questions.length > 0 (fallback to 0 to avoid division by
zero). Reference symbols: q, options, correctIdx, currentIndex, questions,
q.options, q.answer, q.correctAnswerIndex.

In `@eduaid_web/src/pages/Previous.jsx`:
- Line 15: modeIcon currently returns empty strings for both branches, so the
mode badge renders nothing; update the modeIcon(mode) function to return
distinct representations for "interactive" and other modes (e.g., "interactive"
=> an interactive icon/emoji or "🖱️"/"⚡", and default/ "static" => a static
icon/emoji like "📝"/"📄" or the appropriate icon class name). Locate the
modeIcon helper and change its ternary (or switch) to return meaningful
strings/components that the mode badge can render (keep the function signature
modeIcon(mode) and return the same type used by the badge—string, JSX, or icon
class).

In `@eduaid_web/src/pages/Text_Input.jsx`:
- Around line 36-39: The catch blocks in the file upload / Google Doc fetch
flows are writing error messages into the text input state via setText which can
be submitted; create a separate error state (e.g., error and setError) and
replace calls to setText("Error...") with setError(...) in the catch handlers
(and clear error when a successful result sets text), then render that error
separately in the UI near the Text_Input component instead of populating the
text input; apply the same change for the other catch at the locations
referenced (the blocks using setText in the Google Doc/file fetch handlers).
- Around line 234-237: The increment control for numQuestions has no upper
bound; update the onClick handler for the "+" button that uses setNumQuestions
and the numQuestions state to cap increases (e.g., use Math.min(MAX_QUESTIONS, n
+ 1)) and define a reasonable MAX_QUESTIONS constant; also disable the "+"
button (and/or add a title tooltip) when numQuestions >= MAX_QUESTIONS and
validate/enforce the same limit wherever requests are sent (the submit/handler
that uses numQuestions) to prevent backend overload.
- Around line 146-152: The onClick handler calling
navigator.clipboard.readText() can reject (permission denied or unsupported) and
currently has no error handling; update the button's onClick logic (the inline
handler that calls navigator.clipboard.readText().then((t) => setText(t))) to
handle promise rejections by adding a .catch(...) or using async/await with
try/catch, and surface the failure (e.g., log via console.error or show a
user-facing message) while preserving the successful setText(t) path.
- Around line 50-56: The getEndpoint function currently doesn't map the boolean
question type to its hard endpoint; update getEndpoint to handle qType ===
"get_boolq" when diff !== "Easy Difficulty" by returning "get_boolq_hard"
alongside the existing cases for "get_shortq" and "get_mcq", so calls that pass
qType "get_boolq" will use the backend /get_boolq_hard endpoint.

---

Outside diff comments:
In `@eduaid_web/src/pages/StaticQuiz.jsx`:
- Around line 170-179: The loadLogoAsBytes function references logoPNG which is
not defined; add a proper import or definition for logoPNG and update any usages
accordingly so loadLogoAsBytes can fetch the asset without throwing a
ReferenceError. Specifically, add an import statement for the logo (e.g., import
logoPNG from 'path/to/logo.png') near the top of the file where other imports
live, or replace logoPNG with the correct existing symbol if a different
variable already holds the logo; ensure the symbol name matches exactly the one
used in loadLogoAsBytes.
- Around line 121-131: The block in StaticQuiz.jsx that pushes MCQ entries into
combinedQaPairs should only treat qaPairsFromStorage["output"] as MCQ when
either qaPairsFromStorage["output_mcq"] exists OR questionType === "get_mcq" but
only if output_mcq is not present (to avoid duplicate processing); update the if
condition around the combinedQaPairs.push loop (referencing qaPairsFromStorage,
combinedQaPairs, questionType, and output_mcq/output) so it requires output_mcq
OR (questionType === "get_mcq" AND output_mcq is falsy) before iterating over
qaPairsFromStorage["output"], ensuring non-MCQ output arrays are not
misinterpreted and MCQs are not added twice.

---

Nitpick comments:
In `@backend/download_models.py`:
- Around line 39-62: The current regex parsing of Generator/main.py (variables
QG_PRETRAINED, QAE_PRETRAINED, nli_model_name via qg, qae, nli) is brittle;
update download_models.py to read model names from a dedicated shared config
(e.g., a JSON/YAML or small Python module) that main.py also imports, and fall
back to the existing regex only if the config is missing. Specifically:
add/consume a config source (config.json or config.py) that exposes the
QG_PRETRAINED, QAE_PRETRAINED, nli_model_name keys, change the code that
currently builds qg/qae/nli to load from that config first, and keep the
AutoTokenizer/AutoModel* download attempts and error handling unchanged so model
download logic (the try blocks that call AutoTokenizer.from_pretrained,
AutoModelForSeq2SeqLM.from_pretrained,
AutoModelForSequenceClassification.from_pretrained) remains intact as a fallback
when names are obtained.

In `@backend/Generator/mcq.py`:
- Around line 11-21: Extract the duplicated _safe_nltk_download logic into a
single shared utility (e.g., create safe_nltk_download in
backend/Generator/nltk_utils.py) and replace the per-file _safe_nltk_download
implementations with imports from that utility; change the implementation to
call nltk.download with the package name derived from the path (package_name =
pkg.split('/')[-1]) so callers like _safe_nltk_download('corpora/stopwords') and
_safe_nltk_download('corpora/brown') continue to work but the downloader
receives 'stopwords'/'brown', and keep the same try/except behavior to silently
ignore failures.

In `@eduaid_web/src/App.js`:
- Around line 22-28: Routes for "/input" and "/upload" effectively render the
same UI (Text_Input via Text_Input and Upload), so either document the intent or
consolidate: if distinct navigation flows are not required, replace the
redundant Route (either remove <Route path="/upload" element={<Upload />} or
make it redirect to "/input") or else update the Upload component (Upload) to
provide a clearly different UI/props from Text_Input (Text_Input) and add a
comment in App.js explaining why both routes exist; locate the Route entries for
"/" , "/input" (Text_Input), and "/upload" (Upload) in App.js to apply the
change.

In `@eduaid_web/src/components/Header.jsx`:
- Line 1: The file Header.jsx starts with a UTF-8 BOM character before the
import statement; remove the invisible BOM so the first character is the `import
React, { useState } from "react";` line (ensure the file is saved as UTF-8
without BOM). Locate the top of the Header.jsx file and delete the leading BOM,
then re-save the file in UTF-8 without BOM (or use your editor/CLI tool to strip
BOM) so React component imports (and any build/lint steps) no longer see the
invisible character.
- Around line 57-64: The hamburger toggle button in Header.jsx (the button using
onClick={() => setOpen(!open)} and the open state) lacks accessibility
attributes; update the <button> to include type="button", an appropriate
aria-label (e.g., "Toggle navigation"), aria-expanded={open} to reflect the open
state, and aria-controls pointing to the id of the collapsible nav element so
screen readers can identify its purpose and relationship to the menu; ensure the
controlled nav element (the menu) has the matching id.
- Around line 67-90: Add outside-click and Escape-key handlers to close the
mobile menu by wiring document-level event listeners that call setOpen(false)
when appropriate. In the Header component, use a ref (e.g., mobileMenuRef)
pointed at the mobile menu container (the div controlled by open) and add a
mousedown/touchstart listener that checks if the event target is outside
mobileMenuRef.current then calls setOpen(false); also add a keydown listener
that closes on Escape. Remember to add/remove these listeners in useEffect and
clean up on unmount, and ensure the existing onClick handlers for navLinks and
the Generate Quiz Link continue to call setOpen(false).

In `@eduaid_web/src/pages/InteractiveQuiz.jsx`:
- Line 57: The Retry Quiz button in InteractiveQuiz.jsx uses a long inline
onClick handler; extract it into a named function (e.g., handleRetry) inside the
InteractiveQuiz component and have the button call handleRetry instead.
Implement handleRetry to call setCurrentIndex(0), setSelected(null),
setSubmitted(false), setScore(0), and setFinished(false), and replace the inline
arrow function on the <button> with onClick={handleRetry} to improve readability
and testability.

In `@eduaid_web/src/pages/Landing.jsx`:
- Line 1: The import line in Landing.jsx includes unused symbols useEffect and
useRef; remove those from the React import (update the import statement that
currently references React, { useEffect, useRef }) so it only imports React (or
only the symbols actually used) to eliminate the unused-import warning and keep
imports minimal.
- Around line 6-13: The features array in Landing.jsx defines an icon property
for each feature but currently sets icon: "" which results in missing visuals;
update the features constant to either populate icon with actual values (e.g.,
emoji strings like "📄", "🤖", or React icon components imported from a library
such as react-icons) or remove the unused icon property and any rendering that
expects it. Locate the features array and the component that maps over it
(references to features and the icon property) and ensure the renderer uses the
chosen icon values (or conditionally hides the icon element when icon is falsy)
so icons display correctly or the UI omits the empty placeholders.
- Around line 117-120: The footer currently contains a hardcoded year "2026"
which will become stale; update the JSX in Landing.jsx (the <footer> / <p>
element that renders "2026 EduAid Built with for learners everywhere") to
compute the current year at render time (e.g., using JavaScript Date to
getFullYear()) and interpolate that value into the JSX so the displayed year is
always current.

In `@eduaid_web/src/pages/Previous.jsx`:
- Around line 31-34: The handleClear function deletes quiz history immediately;
update handleClear to prompt the user for confirmation before clearing by
showing a confirmation dialog (e.g., using window.confirm or your app's modal)
and only call localStorage.removeItem("last5Quizzes") and setQuizzes([]) if the
user confirms; cancel the operation and return early if the user declines to
avoid accidental data loss.
- Around line 66-68: The current JSX uses the array index `i` as the React key
in the quizzes.map render (the <button key={i} ...> inside the quizzes.map),
which can lead to rendering issues; update the key to use a stable unique
identifier from each quiz (e.g., `quiz.id`, `quiz.timestamp`, or another unique
field) instead of `i`, and if that field may be missing ensure you compute a
stable fallback (e.g., combine a creation timestamp with a unique string) so the
`key` prop on the button is always a consistent unique value.
- Around line 1-4: Remove the unused import logoPNG from the top of the
Previous.jsx module; locate the import statement "import logoPNG from
\"../assets/aossie_logo_transparent.png\"", delete it, and keep the other
imports (React, useNavigate, Link) intact to eliminate the dead dependency and
reduce bundle size.

In `@eduaid_web/src/pages/Question_Type.jsx`:
- Around line 127-144: The navigation is inconsistent: the Continue Link in
Question_Type.jsx uses "/input" while the "Back to input" Link points to
"/upload" (both render Text_Input via App.js), which is confusing; update the
"Back to input" Link to use the same route as Continue ("/input") or rename the
link label to match "/upload" so routes and labels are consistent—locate the two
Link elements (the Link wrapping the Continue button that calls
handleSaveToLocalStorage and the standalone Link with the "Back to input" text)
and make their paths and labels match the intended navigation flow.

In `@eduaid_web/src/pages/QuizModeWrapper.jsx`:
- Around line 6-14: The destructured but unused questions from location.state in
QuizModeWrapper should be removed or propagated: either change the destructuring
to only grab mode (i.e., useLocation() and const { mode } = location.state || {
mode: 'static' }) to eliminate dead code, or pass questions into the child
components (e.g., <InteractiveQuiz questions={questions}/> and <StaticQuiz
questions={questions}/>) and update InteractiveQuiz and StaticQuiz to accept and
use the questions prop; update QuizModeWrapper (the component using useLocation)
accordingly.

In `@eduaid_web/src/pages/StaticQuiz.jsx`:
- Line 133: Change the loose equality check in the condition that tests
questionType to a strict equality to avoid type coercion; specifically update
the conditional that reads if (questionType == "get_boolq") to use === (if
(questionType === "get_boolq")) wherever that check appears (e.g., in the
component/page logic handling questionType in StaticQuiz.jsx).
- Around line 17-28: Replace direct DOM manipulation in the
useEffect/handleClickOutside logic by managing dropdown visibility with React
state (e.g., isPdfDropdownOpen) and a ref (e.g., pdfDropdownRef) on the dropdown
element instead of document.getElementById and classList; update the JSX to
conditionally apply the "hidden" className based on state, change
handleClickOutside to check pdfDropdownRef.current.contains(event.target) to
close via setIsPdfDropdownOpen(false), and keep the existing event listener
cleanup in the useEffect.

In `@eduaid_web/src/pages/Text_Input.jsx`:
- Around line 289-294: Replace the numeric toggle update with a boolean flip to
make intent clearer: in the button onClick use setIsToggleOn(v => !v) instead of
setIsToggleOn((v) => (v + 1) % 2), and ensure the related state variable
isToggleOn is initialized as a boolean (true/false) so the className conditions
remain correct.
- Around line 199-201: The <h2> element for "Quiz Settings" contains conflicting
Tailwind font-size classes (`text-base` and `text-xs`); remove the redundant one
so only the intended size remains (either keep `text-xs` or `text-base`) by
updating the className on the <h2> in Text_Input.jsx to a single font-size class
(e.g., remove `text-base` if you want `text-xs` or remove `text-xs` if you want
`text-base`) to avoid confusion and ensure consistent styling.
- Around line 6-10: QUESTION_TYPES is duplicated in Text_Input.jsx and differs
from the canonical list in Question_Type.jsx; extract the array into a shared
module (e.g., export const QUESTION_TYPES from a new
src/constants/questionTypes.js) and replace the local definitions in both
Text_Input.jsx and Question_Type.jsx with an import of QUESTION_TYPES so both
components (Text_Input.jsx and the Question_Type component) use the single
source of truth, preserving all entries (including get_problems) and icon/desc
fields.
- Around line 62-73: The early return after fetching Google Doc content prevents
question generation from running (see docUrl, apiClient.post("/get_content"),
setText, setLoading); either remove the early return so the flow continues into
the existing question-generation logic, or after successful fetch explicitly
invoke the generation routine (call the existing generateQuestions or
handleGenerate function with the fetched text) and clear loading appropriately;
ensure you reference and reuse the component's question-generation function
rather than forcing the user to click "Generate" again.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc3bf1a and c87aa51.

📒 Files selected for processing (15)
  • backend/._s2v_old
  • backend/Generator/mcq.py
  • backend/Generator/question_filters.py
  • backend/download_models.py
  • backend/server.py
  • eduaid_web/src/App.js
  • eduaid_web/src/components/Header.jsx
  • eduaid_web/src/pages/InteractiveQuiz.jsx
  • eduaid_web/src/pages/Landing.jsx
  • eduaid_web/src/pages/Previous.jsx
  • eduaid_web/src/pages/Question_Type.jsx
  • eduaid_web/src/pages/QuizModeWrapper.jsx
  • eduaid_web/src/pages/StaticQuiz.jsx
  • eduaid_web/src/pages/Text_Input.jsx
  • eduaid_web/src/pages/Upload.jsx

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive UI redesign for EduAid, introducing a modern landing page and an interactive quiz mode where users can answer questions one at a time with real-time feedback. The changes include significant frontend refactoring with a new dark theme using purple-cyan color scheme, improved navigation with a sticky header component, and backend enhancements for better error handling and NLTK resource management.

Changes:

  • Added new Landing page with hero section, features showcase, and modern glassmorphic design
  • Implemented Interactive Quiz mode with one-question-at-a-time navigation, progress tracking, and immediate feedback
  • Redesigned existing pages (Text_Input, Question_Type, Previous, StaticQuiz) with consistent modern UI
  • Added Header component with responsive hamburger menu for mobile
  • Improved backend NLTK downloads with safe fallback handling and added debug logging for file uploads
  • Added download_models.py script for pre-caching HuggingFace models

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
eduaid_web/src/pages/Landing.jsx New landing page with hero, features, steps, and CTA sections
eduaid_web/src/components/Header.jsx New sticky navigation header with mobile hamburger menu
eduaid_web/src/pages/InteractiveQuiz.jsx Interactive quiz component with question-by-question flow and scoring
eduaid_web/src/pages/QuizModeWrapper.jsx Router wrapper to switch between static and interactive modes
eduaid_web/src/pages/Upload.jsx Wrapper page that reuses Text_Input component
eduaid_web/src/pages/Text_Input.jsx Complete redesign with drag-drop, quiz mode selector, and modern controls
eduaid_web/src/pages/Question_Type.jsx Redesigned question type selector with card-based UI
eduaid_web/src/pages/StaticQuiz.jsx Updated styling to match new design system
eduaid_web/src/pages/Previous.jsx Redesigned quiz history with modern card layout
eduaid_web/src/App.js Added conditional header rendering and new routes
backend/server.py Improved NLTK downloads, Google Docs error handling, and upload debugging
backend/download_models.py New script for pre-downloading required ML models
backend/Generator/question_filters.py Safe NLTK download wrapper
backend/Generator/mcq.py Safe NLTK download wrapper
backend/._s2v_old macOS metadata file (should not be committed)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
eduaid_web/src/pages/InteractiveQuiz.jsx (1)

65-69: ⚠️ Potential issue | 🟠 Major

Harden correct-answer resolution for malformed question payloads.

Line 68 can resolve to correctIdx = -1 when q.answer is absent and q.correctAnswerIndex is invalid/out-of-range, which breaks scoring/feedback and can dead-end the flow for malformed questions.

Suggested defensive fix
-  const q = questions[currentIndex];
-  const options = q.options ? [...q.options] : [];
-  if (q.answer && !options.includes(q.answer)) options.push(q.answer);
-  const correctIdx = options.indexOf(q.answer ?? options[q.correctAnswerIndex]);
-  const progress = ((currentIndex + 1) / questions.length) * 100;
+  const q = questions[currentIndex] ?? {};
+  const options = Array.isArray(q.options) ? [...q.options] : [];
+  const indexedAnswer =
+    Number.isInteger(q.correctAnswerIndex) &&
+    q.correctAnswerIndex >= 0 &&
+    q.correctAnswerIndex < options.length
+      ? options[q.correctAnswerIndex]
+      : undefined;
+  const answer = q.answer ?? indexedAnswer;
+  if (answer !== undefined && !options.includes(answer)) options.push(answer);
+  const correctIdx = answer !== undefined ? options.indexOf(answer) : -1;
+  const progress = questions.length > 0 ? ((currentIndex + 1) / questions.length) * 100 : 0;
🧹 Nitpick comments (1)
eduaid_web/src/pages/InteractiveQuiz.jsx (1)

23-23: Avoid nested interactive elements (Link wrapping button).

Using a button inside Link creates invalid interactive nesting and hurts accessibility semantics. Style the Link directly as the button.

Suggested markup cleanup
-          <Link to="/upload"><button className="px-6 py-3 rounded-xl bg-gradient-to-r from-[`#7600F2`] to-[`#00CBE7`] text-white font-bold">Generate a Quiz </button></Link>
+          <Link to="/upload" className="inline-block px-6 py-3 rounded-xl bg-gradient-to-r from-[`#7600F2`] to-[`#00CBE7`] text-white font-bold">
+            Generate a Quiz
+          </Link>
...
-            <Link to="/upload"><button className="px-6 py-3 rounded-xl bg-gradient-to-r from-[`#7600F2`] to-[`#00CBE7`] text-white font-bold hover:scale-105 transition-all">New Quiz </button></Link>
+            <Link to="/upload" className="inline-block px-6 py-3 rounded-xl bg-gradient-to-r from-[`#7600F2`] to-[`#00CBE7`] text-white font-bold hover:scale-105 transition-all">
+              New Quiz
+            </Link>

Also applies to: 58-58

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/InteractiveQuiz.jsx` at line 23, Replace the nested
<button> inside <Link> in InteractiveQuiz.jsx by styling the Link itself (e.g.,
<Link to="/upload" className="px-6 py-3 rounded-xl bg-gradient-to-r
from-[`#7600F2`] to-[`#00CBE7`] text-white font-bold">Generate a Quiz</Link>) so you
avoid interactive nesting; do the same for the second occurrence where Link
currently wraps a button. Ensure any click/keyboard behavior remains accessible
(use role="button" and aria-label only if needed) and remove the inner <button>
element.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@eduaid_web/src/pages/InteractiveQuiz.jsx`:
- Line 23: Replace the nested <button> inside <Link> in InteractiveQuiz.jsx by
styling the Link itself (e.g., <Link to="/upload" className="px-6 py-3
rounded-xl bg-gradient-to-r from-[`#7600F2`] to-[`#00CBE7`] text-white
font-bold">Generate a Quiz</Link>) so you avoid interactive nesting; do the same
for the second occurrence where Link currently wraps a button. Ensure any
click/keyboard behavior remains accessible (use role="button" and aria-label
only if needed) and remove the inner <button> element.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c87aa51 and ee39228.

📒 Files selected for processing (1)
  • eduaid_web/src/pages/InteractiveQuiz.jsx

Backend (server.py):
- Remove unused imports (pprint, spacy, STOP_WORDS, punctuation, nlargest, webbrowser)
- Move OAuth2 imports to lazy loading inside /generate_gform endpoint
- Harden CORS: restrict origins to localhost:3000 and localhost:19222
- Add input validation (validate_input, validate_max_questions) on all endpoints
- Add file upload security: 10MB limit, extension whitelist (pdf/txt/docx/doc)
- Add YouTube transcript security: video ID regex, subprocess timeout, path traversal prevention
- Replace all print() with structured logging module
- Wrap all generators in try/except with graceful None fallback
- Add /health endpoint and proper error handlers (400/500)
- Add availability checks (503) for all model-dependent endpoints
- Restore missing initializations (answer, qg, qa_model, make_question_harder)

Frontend:
- Remove unused logoPNG import (eduaid_web/Previous.jsx)
- Remove unused stars, FaClipboard, Switch imports and dead isToggleOn state (extension/Answer.jsx)
- Remove commented-out Switch JSX block (extension/Answer.jsx)
- Remove unused FaClipboard import (extension/Previous.jsx)
- Add request timeout (120s) and AbortController to apiClient.js
- Add safe JSON parsing with error handling in apiClient.js
- Deduplicate postFormData method in apiClient.js
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
eduaid_web/src/pages/StaticQuiz.jsx (1)

93-204: ⚠️ Potential issue | 🟡 Minor

Missing questionType in useEffect dependency array.

The useEffect reads questionType (lines 170, 180, 188) to determine how to parse localStorage data, but only questionsProp is listed in the dependency array (line 204). If questionType changes after mount, the effect won't re-run and the questions won't be re-parsed correctly.

🔧 Add missing dependency
-  }, [questionsProp]);
+  }, [questionsProp, questionType]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/StaticQuiz.jsx` around lines 93 - 204, The effect in
useEffect that parses questions from localStorage depends on questionType but
only lists questionsProp in its dependency array, so update the dependency array
to include questionType (i.e., change the dependencies for the useEffect that
sets qaPairs to [questionsProp, questionType]) so the effect re-runs when
questionType changes and setQaPairs is called with correctly parsed data; locate
the useEffect block handling questionsProp/localStorage and adjust its
dependencies accordingly.
extension/src/pages/previous/Previous.jsx (1)

9-12: ⚠️ Potential issue | 🟡 Minor

Add error handling for JSON.parse.

If localStorage data is corrupted or manually modified, JSON.parse will throw and crash the component. Wrap in try/catch to fail gracefully.

🛡️ Proposed fix
 const getQuizzesFromLocalStorage = () => {
   const quizzes = localStorage.getItem('last5Quizzes');
-  return quizzes ? JSON.parse(quizzes) : [];
+  if (!quizzes) return [];
+  try {
+    return JSON.parse(quizzes);
+  } catch {
+    return [];
+  }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extension/src/pages/previous/Previous.jsx` around lines 9 - 12,
getQuizzesFromLocalStorage currently calls JSON.parse directly which can throw
if localStorage['last5Quizzes'] is corrupted; wrap the parse in a try/catch
inside the getQuizzesFromLocalStorage function (in Previous.jsx), return an
empty array on failure, and optionally log the error (e.g., console.error or a
logger) and remove/ignore the bad localStorage entry so the component doesn't
crash.
♻️ Duplicate comments (1)
eduaid_web/src/pages/Previous.jsx (1)

14-14: ⚠️ Potential issue | 🟡 Minor

Mode badge icon helper still renders nothing.

modeIcon returns "" for both branches, so the badge at Line 75 cannot communicate mode.

Suggested fix
-const modeIcon = (mode) => (mode === "interactive" ? "" : "");
+const modeIcon = (mode) => (mode === "interactive" ? "⚡" : "📋");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Previous.jsx` at line 14, modeIcon currently returns an
empty string for both branches so the mode badge never shows an icon; update the
modeIcon(mode) helper to return distinct values (e.g., an icon component, emoji,
or svg/className) for the "interactive" branch and the other branch (e.g.,
"recorded"/"playback") and provide a sensible default for unknown modes; ensure
the badge rendering that calls modeIcon uses the returned value directly (or
wraps it in the Badge JSX) so the correct icon is visible.
🧹 Nitpick comments (6)
extension/src/pages/previous/Previous.jsx (1)

104-104: ReactDOM.render is deprecated in React 18 and will be removed in React 19.

The extension uses React 18.2.0, where ReactDOM.render is deprecated in favor of createRoot from react-dom/client. This pattern appears in 7 entry point files across the extension (Previous.jsx, Popup.jsx, Home.jsx, Question.jsx, SidePanel.jsx, TextInput.jsx, and Answer.jsx). The web app already uses the correct pattern.

Update the import:

-import ReactDOM from 'react-dom';
+import { createRoot } from 'react-dom/client';

Update the render call:

-ReactDOM.render(<Previous />, document.getElementById('root'));
+createRoot(document.getElementById('root')).render(<Previous />);

Consider addressing all 7 entry points consistently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extension/src/pages/previous/Previous.jsx` at line 104, Replace the
deprecated ReactDOM.render usage in Previous.jsx by importing createRoot from
'react-dom/client' and using
createRoot(document.getElementById('root')).render(<Previous />); locate the
ReactDOM.render call for the Previous component and switch to the createRoot
pattern, and apply the same change to the other entry points that call
ReactDOM.render (Popup.jsx, Home.jsx, Question.jsx, SidePanel.jsx,
TextInput.jsx, Answer.jsx) so all entry files consistently import createRoot and
call createRoot(...).render(...) instead of ReactDOM.render.
backend/Generator/encoding.py (1)

12-17: Consider adding local validation in beam_search_decoding() for defense-in-depth.

While the API routes already validate max_questions to the range [1, 50] via validate_max_questions() in server.py, adding local bounds checking in this function would prevent issues if the code is ever refactored or used in new ways. The current implementation relies entirely on upstream validation, which is a valid but fragile pattern. Optional: Add a simple check like num = min(num, 50) at line 12 to be more resilient to unexpected inputs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/Generator/encoding.py` around lines 12 - 17, Add defensive local
validation inside beam_search_decoding(): clamp the incoming count (num) into
the allowed range (e.g., num = max(1, min(num, 50))) before computing num_beams,
and then recompute num_beams = max(10, num) so model.generate(input_ids=inp_ids,
..., num_beams=num_beams, num_return_sequences=num) can never be called with
out-of-range values; update any related local variables (num and num_beams)
accordingly.
backend/Generator/mcq.py (2)

136-151: Performance concern: spaCy model loaded on every call.

The spaCy model en_core_web_sm is loaded inside this function, which gets called repeatedly during question generation. This is inefficient since model loading is expensive. The model is already loaded at module initialization in main.py (self.nlp = spacy.load('en_core_web_sm')).

Consider accepting the NLP model as a parameter or loading it once at module level.

♻️ Proposed refactor to accept model as parameter
-def extract_noun_phrases(text):
+def extract_noun_phrases(text, nlp=None):
     """Extract noun phrases using spaCy instead of pke"""
     out = []
     try:
-        nlp = spacy.load('en_core_web_sm')
+        if nlp is None:
+            nlp = spacy.load('en_core_web_sm')
         doc = nlp(text)

Then update the call in identify_keywords (line 173) to pass the existing nlp_model:

-    keywords = extract_noun_phrases(text)
+    keywords = extract_noun_phrases(text, nlp_model)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/Generator/mcq.py` around lines 136 - 151, The extract_noun_phrases
function currently reloads the spaCy model on every call; change it to accept an
nlp model parameter (e.g., def extract_noun_phrases(text, nlp):) and remove
spacy.load from inside the function, then update callers such as
identify_keywords to pass the preloaded model (use the existing self.nlp from
main.py or a module-level nlp instance) so the model is loaded only once; ensure
the function signature change and all call sites are updated accordingly and
keep the same return/error behavior.

11-21: Consider consolidating duplicated _safe_nltk_download helper.

This helper function is duplicated across three files: mcq.py, question_filters.py (lines 9-16), and server.py (lines 20-27). Consider extracting it to a shared utility module to avoid maintenance burden and subtle inconsistencies (e.g., server.py logs warnings while this version silently passes).

Additionally, the static analysis correctly flags the silent except: pass pattern. Consider logging the error for debugging purposes.

♻️ Proposed improvement to add minimal logging
 def _safe_nltk_download(pkg):
     try:
         nltk.data.find(pkg)
     except LookupError:
         try:
             nltk.download(pkg, quiet=True, raise_on_error=False)
-        except Exception:
-            pass
+        except Exception as e:
+            print(f"Warning: Failed to download NLTK package {pkg}: {e}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/Generator/mcq.py` around lines 11 - 21, Extract the duplicated helper
_safe_nltk_download into a shared utility (e.g., utils/nltk_utils.py) and
replace the copies in mcq.py, question_filters.py, and server.py with imports
from that module so there’s a single source of truth; update the implementation
of _safe_nltk_download to log any unexpected exceptions instead of silently
passing (use the existing module logger or pass in a logger parameter) and keep
the same behavior for successful finds and download attempts (nltk.data.find and
nltk.download with quiet=True). Ensure callers in Generator.mcq (function/module
referencing _safe_nltk_download) import the new function and remove their local
definitions.
backend/server.py (2)

346-382: Consider using strict=True in zip for safety.

While the length check on line 338 validates matching lengths, using strict=True in the zip() call provides an additional safety net against future code modifications that might bypass the check.

♻️ Proposed improvement
-        for question, options in zip(input_questions, input_options):
+        for question, options in zip(input_questions, input_options, strict=True):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/server.py` around lines 346 - 382, The loop using
zip(input_questions, input_options) should use zip(input_questions,
input_options, strict=True) to ensure mismatched lengths raise immediately;
update the for-loop header (where input_questions and input_options are
iterated) to pass strict=True and keep the existing pre-checks and exception
handling (references: input_questions, input_options, qa_model, TfidfVectorizer,
outputs, logger) so any future length mismatch fails fast instead of silently
truncating.

52-107: Robust initialization with graceful degradation.

The try/except pattern for each generator allows the server to start even if some components fail to load. This is a good resilience pattern.

Consider using logger.exception() instead of logger.error() to automatically include stack traces for easier debugging.

♻️ Example improvement for one block
 try:
     MCQGen = main.MCQGenerator()
     logger.info("MCQGenerator loaded")
 except Exception as e:
-    logger.error(f"Failed to load MCQGenerator: {e}")
+    logger.exception("Failed to load MCQGenerator")
     MCQGen = None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/server.py` around lines 52 - 107, Replace the bare
logger.error/logger.warning calls in the initialization try/excepts with
logger.exception so exceptions include stack traces; update the except blocks
for main.MCQGenerator()/MCQGen, main.BoolQGenerator()/BoolQGen,
main.ShortQGenerator()/ShortQGen, main.FileProcessor()/file_processor,
MediaWikiAPI()/mediawikiapi, main.AnswerPredictor()/answer,
main.QuestionGenerator()/qg, and the QA pipeline/qa_model to call
logger.exception(...) (preserving the current message text) before assigning
None, ensuring full tracebacks are recorded for easier debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/Generator/mcq.py`:
- Around line 75-90: Remove the unnecessary f-string prefixes in the fallback
list (they have no placeholders) and add a boolean flag to track whether
sense2vec produced sufficient original choices before fallbacks so the algorithm
indicator is accurate; specifically, before the fallback block capture a flag
like original_count = len(choices) or used_sense2vec = len(choices) >= 3, run
the existing fallback append logic using the non-f-string list items, then
return choices and "sense2vec" only if used_sense2vec was true (otherwise return
"fallback"). Ensure references to variables in this change target the existing
names choices, answer, and fallbacks in the same function.

In `@backend/server.py`:
- Around line 832-836: The call to qg.generate uses an invalid answer_style
"true_false" which will raise a ValueError; update the call in server.py (the
generated = qg.generate(...) invocation) to use one of the valid styles defined
in Generator.main (e.g. "all", "sentences", or "multiple_choice") or make
answer_style configurable and map any incoming "true_false" option to an allowed
style before calling qg.generate; ensure the parameter passed matches the valid
set from backend/Generator/main.py.
- Around line 467-479: The bug is that predict_boolean_answer
(AnswerPredictor.predict_boolean_answer) expects a list of questions but the
loop passes a single string, causing per-character iteration and a list return;
fix by calling predict_boolean_answer with input_question set to a single-item
list (e.g. [question]) and then extract the first element of the returned list
(qa_response = returned_list[0]) before appending "True"/"False" to output;
update the block that calls answer.predict_boolean_answer to wrap question in a
list and index the result.
- Around line 854-905: The upload_file endpoint currently allows '.doc' in
ALLOWED_EXTENSIONS but FileProcessor.process_file does not support '.doc',
causing silent empty results; remove 'doc' from ALLOWED_EXTENSIONS (leave
'pdf','txt','docx'), ensure the validation logic that computes file_ext uses the
same set, and confirm any user-facing error text (in upload_file) lists only
supported types so validation matches FileProcessor.process_file behavior.

In `@eduaid_web/src/pages/InteractiveQuiz.jsx`:
- Around line 154-158: The feedback block can show "undefined" when correctIdx
=== -1 because options[correctIdx] is accessed; update the rendering logic in
the submitted feedback (the JSX that references submitted, selected, correctIdx
and options) to guard against an invalid index by checking that correctIdx is a
valid array index (e.g., typeof correctIdx === "number" && correctIdx >= 0 &&
correctIdx < options.length) before using options[correctIdx], and use a safe
fallback string like "unknown" or "unavailable" when it's not valid so the
message never displays "undefined".

In `@eduaid_web/src/pages/Previous.jsx`:
- Around line 19-22: The getQuizzes function currently just returns the parsed
JSON which can be a valid non-array (e.g., {}) and later crash on quizzes.map;
change getQuizzes to parse localStorage.getItem("last5Quizzes") into a variable,
verify Array.isArray(parsed) before returning it, and otherwise return []; apply
the same array-checking fix to the other similar getter at lines 65-66 (the
other localStorage parse) so both only return arrays.
- Around line 56-60: Replace the nested <button> inside the Link with a single
Link element styled as the button: remove the <button> element, move its
className ("px-6 py-3 rounded-xl bg-gradient-to-r from-[`#7600F2`] to-[`#00CBE7`]
text-white font-bold hover:scale-105 transition-all") onto the Link component
used for navigation (Link to="/upload"), and render the text "Generate a Quiz"
directly inside the Link; ensure the Link remains from react-router-dom and, if
needed for semantics/accessibility, add role="button" and an appropriate
aria-label.

In `@eduaid_web/src/pages/StaticQuiz.jsx`:
- Around line 126-128: The code currently does
JSON.parse(localStorage.getItem("qaPairs")) directly which can throw on
malformed data; update the StaticQuiz.jsx logic (where qaPairsFromStorage is
set) to wrap the parse in a try-catch, assign an empty object on error, and
optionally log the parse error (e.g., using console.error) so qaPairsFromStorage
is always a safe object rather than letting the component crash.

In `@eduaid_web/src/pages/Text_Input.jsx`:
- Around line 146-149: The implicit return from the arrow callback used on
responseData.output_boolq.Boolean_Questions (inside Text_Input.jsx) causes a
lint warning because Array.forEach callbacks must not return values; update the
forEach call so the callback uses block syntax and calls
questionsArray.push(normalizeQuestion(q, "Boolean")) without returning anything
(i.e., replace the concise arrow body with {
questionsArray.push(normalizeQuestion(q, "Boolean")); }), or alternatively
replace the forEach with a non-returning pattern like
questionsArray.push(...responseData.output_boolq.Boolean_Questions.map(q =>
normalizeQuestion(q, "Boolean"))) to match other branches.
- Around line 101-104: The Boolean normalization currently hardcodes answer:
"True" (see the branch where typeof q === "string" in Text_Input.jsx), which
causes validation errors; instead, after creating the normalized questions array
(items whose question_type === "Boolean"), call the backend /get_boolean_answer
endpoint (predict_boolean_answer) with the list of Boolean question strings,
then update each Boolean question object's answer field with the returned
predicted answer before rendering or validating; ensure you replace the
hardcoded answer in the q string branch and run the fetch/update step once all
questions are normalized so answers align with backend predictions.

In `@eduaid_web/src/utils/apiClient.js`:
- Around line 77-109: postFormData currently does a direct fetch and bypasses
the Electron IPC path used by makeRequest/post; update postFormData to check
this.isElectron and, when true, route the request through the same IPC surface
(window.electronAPI.makeApiRequest) or delegate to the existing makeRequest/post
path so uploads follow the app's IPC contract; when routing via IPC ensure the
FormData payload is serialized/handled consistently with how makeRequest expects
bodies (or document why FormData must use direct fetch if IPC can't transmit
it), and keep the existing timeout/abort behavior when delegating.

In `@extension/src/pages/answer/Answer.jsx`:
- Line 5: The component is rendering FaClipboard but the import was removed;
restore the missing icon import or remove its usage: either add back
"FaClipboard" to the import from "react-icons/fa" alongside FaQuestionCircle and
FaClipboardList, or replace/remove the JSX that references FaClipboard in
Answer.jsx (the rendered element that uses FaClipboard) so there are no
undefined symbol references.

---

Outside diff comments:
In `@eduaid_web/src/pages/StaticQuiz.jsx`:
- Around line 93-204: The effect in useEffect that parses questions from
localStorage depends on questionType but only lists questionsProp in its
dependency array, so update the dependency array to include questionType (i.e.,
change the dependencies for the useEffect that sets qaPairs to [questionsProp,
questionType]) so the effect re-runs when questionType changes and setQaPairs is
called with correctly parsed data; locate the useEffect block handling
questionsProp/localStorage and adjust its dependencies accordingly.

In `@extension/src/pages/previous/Previous.jsx`:
- Around line 9-12: getQuizzesFromLocalStorage currently calls JSON.parse
directly which can throw if localStorage['last5Quizzes'] is corrupted; wrap the
parse in a try/catch inside the getQuizzesFromLocalStorage function (in
Previous.jsx), return an empty array on failure, and optionally log the error
(e.g., console.error or a logger) and remove/ignore the bad localStorage entry
so the component doesn't crash.

---

Duplicate comments:
In `@eduaid_web/src/pages/Previous.jsx`:
- Line 14: modeIcon currently returns an empty string for both branches so the
mode badge never shows an icon; update the modeIcon(mode) helper to return
distinct values (e.g., an icon component, emoji, or svg/className) for the
"interactive" branch and the other branch (e.g., "recorded"/"playback") and
provide a sensible default for unknown modes; ensure the badge rendering that
calls modeIcon uses the returned value directly (or wraps it in the Badge JSX)
so the correct icon is visible.

---

Nitpick comments:
In `@backend/Generator/encoding.py`:
- Around line 12-17: Add defensive local validation inside
beam_search_decoding(): clamp the incoming count (num) into the allowed range
(e.g., num = max(1, min(num, 50))) before computing num_beams, and then
recompute num_beams = max(10, num) so model.generate(input_ids=inp_ids, ...,
num_beams=num_beams, num_return_sequences=num) can never be called with
out-of-range values; update any related local variables (num and num_beams)
accordingly.

In `@backend/Generator/mcq.py`:
- Around line 136-151: The extract_noun_phrases function currently reloads the
spaCy model on every call; change it to accept an nlp model parameter (e.g., def
extract_noun_phrases(text, nlp):) and remove spacy.load from inside the
function, then update callers such as identify_keywords to pass the preloaded
model (use the existing self.nlp from main.py or a module-level nlp instance) so
the model is loaded only once; ensure the function signature change and all call
sites are updated accordingly and keep the same return/error behavior.
- Around line 11-21: Extract the duplicated helper _safe_nltk_download into a
shared utility (e.g., utils/nltk_utils.py) and replace the copies in mcq.py,
question_filters.py, and server.py with imports from that module so there’s a
single source of truth; update the implementation of _safe_nltk_download to log
any unexpected exceptions instead of silently passing (use the existing module
logger or pass in a logger parameter) and keep the same behavior for successful
finds and download attempts (nltk.data.find and nltk.download with quiet=True).
Ensure callers in Generator.mcq (function/module referencing
_safe_nltk_download) import the new function and remove their local definitions.

In `@backend/server.py`:
- Around line 346-382: The loop using zip(input_questions, input_options) should
use zip(input_questions, input_options, strict=True) to ensure mismatched
lengths raise immediately; update the for-loop header (where input_questions and
input_options are iterated) to pass strict=True and keep the existing pre-checks
and exception handling (references: input_questions, input_options, qa_model,
TfidfVectorizer, outputs, logger) so any future length mismatch fails fast
instead of silently truncating.
- Around line 52-107: Replace the bare logger.error/logger.warning calls in the
initialization try/excepts with logger.exception so exceptions include stack
traces; update the except blocks for main.MCQGenerator()/MCQGen,
main.BoolQGenerator()/BoolQGen, main.ShortQGenerator()/ShortQGen,
main.FileProcessor()/file_processor, MediaWikiAPI()/mediawikiapi,
main.AnswerPredictor()/answer, main.QuestionGenerator()/qg, and the QA
pipeline/qa_model to call logger.exception(...) (preserving the current message
text) before assigning None, ensuring full tracebacks are recorded for easier
debugging.

In `@extension/src/pages/previous/Previous.jsx`:
- Line 104: Replace the deprecated ReactDOM.render usage in Previous.jsx by
importing createRoot from 'react-dom/client' and using
createRoot(document.getElementById('root')).render(<Previous />); locate the
ReactDOM.render call for the Previous component and switch to the createRoot
pattern, and apply the same change to the other entry points that call
ReactDOM.render (Popup.jsx, Home.jsx, Question.jsx, SidePanel.jsx,
TextInput.jsx, Answer.jsx) so all entry files consistently import createRoot and
call createRoot(...).render(...) instead of ReactDOM.render.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee39228 and df0e009.

📒 Files selected for processing (12)
  • backend/Generator/encoding.py
  • backend/Generator/main.py
  • backend/Generator/mcq.py
  • backend/server.py
  • eduaid_web/src/pages/InteractiveQuiz.jsx
  • eduaid_web/src/pages/Previous.jsx
  • eduaid_web/src/pages/QuizModeWrapper.jsx
  • eduaid_web/src/pages/StaticQuiz.jsx
  • eduaid_web/src/pages/Text_Input.jsx
  • eduaid_web/src/utils/apiClient.js
  • extension/src/pages/answer/Answer.jsx
  • extension/src/pages/previous/Previous.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • eduaid_web/src/pages/QuizModeWrapper.jsx

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 20 changed files in this pull request and generated 11 comments.

Comments suppressed due to low confidence (1)

eduaid_web/src/pages/StaticQuiz.jsx:207

  • The frontend reads result.form_link from the /generate_gform response, but the backend was changed in this PR and now returns responder_uri and edit_uri (not form_link). As a result, formUrl will always be undefined, and window.open(undefined, "_blank") will open a blank tab instead of the Google Form. The field should be updated to use result.responder_uri or result.edit_uri to match the updated backend response schema.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

… & improve robustness

- download_models.py: platform-agnostic cache paths, remove unused sys import
- Extract _safe_nltk_download into shared Generator/nltk_utils.py (was duplicated in server.py, mcq.py, question_filters.py)
- InteractiveQuiz.jsx: fix options=[undefined] bug, defensive correctIdx handling
- Text_Input.jsx: add get_boolq_hard endpoint, cap numQuestions at 20, clipboard error handling, boolean toggle, better error feedback
- StaticQuiz.jsx: fix MCQ option deduplication (answer was appended even if already in options)
- Landing.jsx: remove unused useEffect/useRef imports, dynamic copyright year
- Header.jsx: add aria-label/aria-expanded for mobile menu accessibility
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (9)
eduaid_web/src/components/Header.jsx (3)

49-53: Avoid nesting <button> inside <Link>.

Placing a <button> element inside a <Link> (which renders as an <a> element) creates invalid HTML—an interactive element nested inside another interactive element. This can cause accessibility issues with screen readers and unexpected behavior.

♻️ Proposed fix: style the Link as a button
         <div className="hidden md:block">
-          <Link to="/upload">
-            <button className="px-5 py-2 rounded-xl bg-gradient-to-r from-[`#7600F2`] to-[`#00CBE7`] text-white text-sm font-bold hover:shadow-[0_0_20px_rgba(118,0,242,0.5)] transition-all duration-300 hover:scale-105">
-              Generate Quiz 
-            </button>
-          </Link>
+          <Link
+            to="/upload"
+            className="inline-block px-5 py-2 rounded-xl bg-gradient-to-r from-[`#7600F2`] to-[`#00CBE7`] text-white text-sm font-bold hover:shadow-[0_0_20px_rgba(118,0,242,0.5)] transition-all duration-300 hover:scale-105"
+          >
+            Generate Quiz
+          </Link>
         </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/components/Header.jsx` around lines 49 - 53, The JSX nests a
<button> inside a <Link> in Header.jsx (the Link to="/upload" wrapping the
Generate Quiz button), which is invalid HTML and causes accessibility issues;
replace the inner <button> with a styled <Link> (or use the Link's component
prop) so the Link itself has the button classes (e.g., move the className "px-5
py-2 rounded-xl bg-gradient-to-r ..." onto the Link or render Link as a
button-like element) and remove the nested <button> to keep a single interactive
element.

69-92: Consider closing mobile menu on outside click or route change.

Currently, the mobile menu only closes when a link inside it is clicked. Users may expect the menu to close when tapping outside of it. This is a nice-to-have UX enhancement.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/components/Header.jsx` around lines 69 - 92, Add
closing-on-outside-click and close-on-route-change behavior for the mobile menu:
wrap the mobile menu container in a ref (the div controlling open/max-h) and add
an effect that registers a mousedown/touchstart handler to call setOpen(false)
when an event target is outside that ref, and also subscribe to route changes
via react-router's useLocation inside the same effect to call setOpen(false) on
location.pathname change; ensure the effect cleans up the event listeners and
only runs when `open` changes. Reference symbols: open, setOpen, isActive,
navLinks, and the mobile menu container div that renders the mapped Link items.

86-90: Same issue: avoid nesting <button> inside <Link> in mobile menu.

This has the same semantic HTML issue as the desktop CTA. Apply the same fix here for consistency.

♻️ Proposed fix
-          <Link to="/upload" onClick={() => setOpen(false)}>
-            <button className="w-full mt-2 px-5 py-3 rounded-xl bg-gradient-to-r from-[`#7600F2`] to-[`#00CBE7`] text-white text-sm font-bold">
-              Generate Quiz 
-            </button>
-          </Link>
+          <Link
+            to="/upload"
+            onClick={() => setOpen(false)}
+            className="block w-full mt-2 px-5 py-3 rounded-xl bg-gradient-to-r from-[`#7600F2`] to-[`#00CBE7`] text-white text-sm font-bold text-center"
+          >
+            Generate Quiz
+          </Link>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/components/Header.jsx` around lines 86 - 90, The mobile menu
currently nests a <button> inside the React Router <Link> (the block using Link
to="/upload" and setOpen(false)), which is invalid HTML; replace the nested
button with a styled <Link> that carries the same className and onClick handler
(call setOpen(false) from the Link's onClick) or alternatively convert it to a
<button> and perform navigation programmatically (useNavigate) in the onClick;
update the element that currently has className="w-full mt-2 px-5 py-3
rounded-xl bg-gradient-to-r from-[`#7600F2`] to-[`#00CBE7`] text-white text-sm
font-bold" so the visual styling remains while removing the button-inside-link
nesting.
eduaid_web/src/pages/Text_Input.jsx (1)

6-10: Duplicate QUESTION_TYPES definition.

This file defines its own QUESTION_TYPES array, but a more comprehensive version exists in Question_Type.jsx (lines 5-34) with additional metadata like icons and gradients. Consider importing from a shared constants file to maintain consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/Text_Input.jsx` around lines 6 - 10, Duplicate
QUESTION_TYPES array is defined here; replace the local constant with an import
of the shared definition to keep metadata (icons, gradients) consistent. Remove
the local QUESTION_TYPES in Text_Input.jsx and import the exported
QUESTION_TYPES (or the canonical constant) from Question_Type.jsx (or a shared
constants module if present), then update any references in Text_Input.jsx to
use that imported symbol. Ensure the import uses the same exported name
(QUESTION_TYPES) and that any component props expecting the extra metadata still
work after switching.
backend/server.py (2)

956-960: Unused variable result from subprocess call.

The result variable is assigned but never used. Either remove the assignment or use result.stdout/result.stderr for logging.

🧹 Remove unused assignment
-            result = subprocess.run(
+            subprocess.run(
                 ["yt-dlp", "--write-auto-sub", "--sub-lang", "en", "--skip-download",
                  "--sub-format", "vtt", "-o", output_path,
                  f"https://www.youtube.com/watch?v={video_id}"],
                 check=True, capture_output=True, text=True, timeout=60
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/server.py` around lines 956 - 960, The subprocess.run call assigns to
the unused variable result; either drop the assignment or actually use result
for diagnostics—locate the subprocess.run invocation (the line assigning result
= subprocess.run([...], check=True, capture_output=True, text=True, timeout=60))
and either remove the "result =" prefix or keep it and add logging of
result.stdout and/or result.stderr (e.g.,
processLogger.info/debug(result.stdout) and processLogger.error(result.stderr)
on non-empty output) so the captured output isn't discarded.

31-39: CORS origins are hardcoded for development.

The CORS configuration restricts origins to localhost:3000 and localhost:19222. For production deployment, consider using environment variables to configure allowed origins.

♻️ Environment-based CORS configuration
+import os
+
+ALLOWED_ORIGINS = os.environ.get(
+    'CORS_ORIGINS', 
+    'http://localhost:3000,http://localhost:19222'
+).split(',')
+
 CORS(app, resources={
     r"/*": {
-        "origins": ["http://localhost:3000", "http://localhost:19222"],
+        "origins": ALLOWED_ORIGINS,
         "methods": ["GET", "POST", "OPTIONS"],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/server.py` around lines 31 - 39, The CORS setup currently hardcodes
origins in the CORS(app, resources={...}) call; update it to read allowed
origins from an environment variable (e.g., ALLOWED_ORIGINS) and fall back to
the current localhost list for development, parsing a comma-separated string
into a list before passing it to CORS; keep the same resource pattern, methods,
headers and max_age and ensure the parsed value is used for the "origins" key in
the existing CORS(...) invocation so production origins can be configured
without code changes.
backend/Generator/nltk_utils.py (1)

10-13: Consider logging suppressed download failures for debugging.

The silent pass makes it difficult to diagnose why NLTK resources fail to load in production. While the fault-tolerant design is intentional, a warning log would aid troubleshooting without changing the graceful fallback behavior.

♻️ Optional: Add logging
+import logging
+
+logger = logging.getLogger(__name__)
+
 def safe_nltk_download(pkg):
     """Download an NLTK resource if not already present, suppressing errors."""
     try:
         nltk.data.find(pkg)
     except LookupError:
         try:
             nltk.download(pkg.split('/')[-1], quiet=True, raise_on_error=False)
-        except Exception:
-            pass
+        except Exception as e:
+            logger.debug(f"NLTK download failed for {pkg}: {e}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/Generator/nltk_utils.py` around lines 10 - 13, The try/except around
nltk.download swallows exceptions; update the except in the download block (the
code calling nltk.download) to log a warning including the exception details
instead of silently passing so failures are visible in logs; use the module
logger (e.g., getLogger(__name__)) or an existing logger and call logger.warning
or logger.exception with a clear message like "Failed to download NLTK resource"
plus the exception/context while preserving the current fault-tolerant behavior
(do not re-raise).
backend/Generator/mcq.py (2)

128-143: Consider caching the spaCy model to avoid repeated loading.

spacy.load('en_core_web_sm') is called on every invocation of extract_noun_phrases. Loading spaCy models is expensive (~100ms+). For better performance, cache the model at module level.

♻️ Cache the model at module level
+# Module-level cached model
+_nlp_model = None
+
+def _get_nlp_model():
+    global _nlp_model
+    if _nlp_model is None:
+        _nlp_model = spacy.load('en_core_web_sm')
+    return _nlp_model
+
 def extract_noun_phrases(text):
     """Extract noun phrases using spaCy instead of pke"""
     out = []
     try:
-        nlp = spacy.load('en_core_web_sm')
+        nlp = _get_nlp_model()
         doc = nlp(text)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/Generator/mcq.py` around lines 128 - 143, The function
extract_noun_phrases currently calls spacy.load('en_core_web_sm') on every call;
instead, initialize and cache the model at module level (e.g., a module-scope
variable like _SPACY_NLP or nlp_model) and have extract_noun_phrases reuse that
instance; change the function to use the cached model (lazily load it if None to
preserve behavior), keep the existing try/except around processing (or add a
separate try for model load) and preserve returned values, and ensure references
to spacy.load('en_core_web_sm') are removed from inside extract_noun_phrases so
model loading cost is not incurred per call.

211-214: Placeholder options may confuse users.

When sense2vec fails to generate enough distractors, the fallback appends generic placeholders like "Option 1", "Option 2". These are obviously incorrect and may undermine quiz credibility. Consider using more contextual fallbacks or flagging these questions for review.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/Generator/mcq.py` around lines 211 - 214, The loop that pads options
(while len(options) < 3) currently appends generic placeholders ("Option 1"
etc.); instead, replace that placeholder logic with a contextual fallback:
attempt to generate additional distractors from a lexical resource (e.g.,
WordNet synonyms/related terms derived from the target answer or question
keywords) and only if lexical fallbacks yield nothing then mark the MCQ for
human review by setting a flag (e.g., question.needs_review = True or adding
metadata flagged_for_review) and append clearly labeled review placeholders like
"[Review needed]" rather than "Option X". Update the block that handles
sense2vec failures and the while len(options) < 3 branch to call a helper (e.g.,
get_wordnet_distractors) and to set the review flag when no contextual
distractors can be produced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@eduaid_web/src/components/Header.jsx`:
- Line 1: Remove the Unicode BOM (U+FEFF) at the start of Header.jsx so the
first character of the file begins with the import statement; open the file in
your editor or run a tool to save it as UTF-8 without BOM, ensuring the line
starting with "import React, { useState } from \"react\";" no longer has the
invisible BOM character.

---

Nitpick comments:
In `@backend/Generator/mcq.py`:
- Around line 128-143: The function extract_noun_phrases currently calls
spacy.load('en_core_web_sm') on every call; instead, initialize and cache the
model at module level (e.g., a module-scope variable like _SPACY_NLP or
nlp_model) and have extract_noun_phrases reuse that instance; change the
function to use the cached model (lazily load it if None to preserve behavior),
keep the existing try/except around processing (or add a separate try for model
load) and preserve returned values, and ensure references to
spacy.load('en_core_web_sm') are removed from inside extract_noun_phrases so
model loading cost is not incurred per call.
- Around line 211-214: The loop that pads options (while len(options) < 3)
currently appends generic placeholders ("Option 1" etc.); instead, replace that
placeholder logic with a contextual fallback: attempt to generate additional
distractors from a lexical resource (e.g., WordNet synonyms/related terms
derived from the target answer or question keywords) and only if lexical
fallbacks yield nothing then mark the MCQ for human review by setting a flag
(e.g., question.needs_review = True or adding metadata flagged_for_review) and
append clearly labeled review placeholders like "[Review needed]" rather than
"Option X". Update the block that handles sense2vec failures and the while
len(options) < 3 branch to call a helper (e.g., get_wordnet_distractors) and to
set the review flag when no contextual distractors can be produced.

In `@backend/Generator/nltk_utils.py`:
- Around line 10-13: The try/except around nltk.download swallows exceptions;
update the except in the download block (the code calling nltk.download) to log
a warning including the exception details instead of silently passing so
failures are visible in logs; use the module logger (e.g., getLogger(__name__))
or an existing logger and call logger.warning or logger.exception with a clear
message like "Failed to download NLTK resource" plus the exception/context while
preserving the current fault-tolerant behavior (do not re-raise).

In `@backend/server.py`:
- Around line 956-960: The subprocess.run call assigns to the unused variable
result; either drop the assignment or actually use result for diagnostics—locate
the subprocess.run invocation (the line assigning result = subprocess.run([...],
check=True, capture_output=True, text=True, timeout=60)) and either remove the
"result =" prefix or keep it and add logging of result.stdout and/or
result.stderr (e.g., processLogger.info/debug(result.stdout) and
processLogger.error(result.stderr) on non-empty output) so the captured output
isn't discarded.
- Around line 31-39: The CORS setup currently hardcodes origins in the CORS(app,
resources={...}) call; update it to read allowed origins from an environment
variable (e.g., ALLOWED_ORIGINS) and fall back to the current localhost list for
development, parsing a comma-separated string into a list before passing it to
CORS; keep the same resource pattern, methods, headers and max_age and ensure
the parsed value is used for the "origins" key in the existing CORS(...)
invocation so production origins can be configured without code changes.

In `@eduaid_web/src/components/Header.jsx`:
- Around line 49-53: The JSX nests a <button> inside a <Link> in Header.jsx (the
Link to="/upload" wrapping the Generate Quiz button), which is invalid HTML and
causes accessibility issues; replace the inner <button> with a styled <Link> (or
use the Link's component prop) so the Link itself has the button classes (e.g.,
move the className "px-5 py-2 rounded-xl bg-gradient-to-r ..." onto the Link or
render Link as a button-like element) and remove the nested <button> to keep a
single interactive element.
- Around line 69-92: Add closing-on-outside-click and close-on-route-change
behavior for the mobile menu: wrap the mobile menu container in a ref (the div
controlling open/max-h) and add an effect that registers a mousedown/touchstart
handler to call setOpen(false) when an event target is outside that ref, and
also subscribe to route changes via react-router's useLocation inside the same
effect to call setOpen(false) on location.pathname change; ensure the effect
cleans up the event listeners and only runs when `open` changes. Reference
symbols: open, setOpen, isActive, navLinks, and the mobile menu container div
that renders the mapped Link items.
- Around line 86-90: The mobile menu currently nests a <button> inside the React
Router <Link> (the block using Link to="/upload" and setOpen(false)), which is
invalid HTML; replace the nested button with a styled <Link> that carries the
same className and onClick handler (call setOpen(false) from the Link's onClick)
or alternatively convert it to a <button> and perform navigation
programmatically (useNavigate) in the onClick; update the element that currently
has className="w-full mt-2 px-5 py-3 rounded-xl bg-gradient-to-r from-[`#7600F2`]
to-[`#00CBE7`] text-white text-sm font-bold" so the visual styling remains while
removing the button-inside-link nesting.

In `@eduaid_web/src/pages/Text_Input.jsx`:
- Around line 6-10: Duplicate QUESTION_TYPES array is defined here; replace the
local constant with an import of the shared definition to keep metadata (icons,
gradients) consistent. Remove the local QUESTION_TYPES in Text_Input.jsx and
import the exported QUESTION_TYPES (or the canonical constant) from
Question_Type.jsx (or a shared constants module if present), then update any
references in Text_Input.jsx to use that imported symbol. Ensure the import uses
the same exported name (QUESTION_TYPES) and that any component props expecting
the extra metadata still work after switching.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df0e009 and 78bf5a7.

📒 Files selected for processing (10)
  • backend/Generator/mcq.py
  • backend/Generator/nltk_utils.py
  • backend/Generator/question_filters.py
  • backend/download_models.py
  • backend/server.py
  • eduaid_web/src/components/Header.jsx
  • eduaid_web/src/pages/InteractiveQuiz.jsx
  • eduaid_web/src/pages/Landing.jsx
  • eduaid_web/src/pages/StaticQuiz.jsx
  • eduaid_web/src/pages/Text_Input.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • eduaid_web/src/pages/Landing.jsx

…y fixes

- Remove UTF-8 BOM from Header.jsx
- Cache spaCy model at module level in mcq.py (avoid reload per call)
- Add warning logging to nltk_utils.py instead of silent except
- Log yt-dlp subprocess stdout/stderr instead of discarding
- Read CORS origins from ALLOWED_ORIGINS env var with localhost fallback
- Replace invalid button-inside-Link nesting in Header.jsx (desktop + mobile)
- Add outside-click and route-change close behavior for mobile menu
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (4)
backend/server.py (3)

470-473: ⚠️ Potential issue | 🔴 Critical

Boolean predictor is still called with the wrong payload shape.

On Line 471, input_question is passed as a string, but the predictor path returns list-based results. This can mis-evaluate outputs and produce incorrect True/False values.

🔧 Suggested fix
                 qa_response = answer.predict_boolean_answer(
-                    {"input_text": input_text, "input_question": question}
+                    {"input_text": input_text, "input_question": [question]}
                 )
-                output.append("True" if qa_response else "False")
+                pred = qa_response[0] if isinstance(qa_response, list) and qa_response else False
+                output.append("True" if pred else "False")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/server.py` around lines 470 - 473, The boolean predictor is being
invoked with the wrong payload shape: answer.predict_boolean_answer is passed
{"input_text": input_text, "input_question": question} where input_question must
be a list (the predictor returns list-based results); change the call to pass
input_question as a list (e.g., {"input_text": input_text, "input_question":
[question]}) or otherwise match the predictor's expected list shape and then
index the returned list to produce the single "True"/"False" string; update any
variable names around answer.predict_boolean_answer and qa_response handling to
reflect the list-based response.

829-833: ⚠️ Potential issue | 🟠 Major

answer_style="true_false" is not aligned with generator style contract.

This still risks runtime failure in /get_boolq_hard when calling qg.generate.

🔧 Suggested fix
         generated = qg.generate(
             article=input_text,
             num_questions=input_questions,
-            answer_style="true_false"
+            answer_style="sentences"
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/server.py` around lines 829 - 833, The call to qg.generate in the
/get_boolq_hard flow passes answer_style="true_false", which doesn't match the
generator's expected style contract and can cause runtime failures; update the
answer_style argument in qg.generate to the exact allowed value or enum used by
the generator (e.g., the constant or string defined in the generator
implementation such as AnswerStyle.BOOLQ or the provider's expected
"boolq"/"boolean" token), and add a pre-call validation or mapping for
answer_style in the /get_boolq_hard handler to convert/validate incoming styles
before invoking qg.generate (and wrap the call in a try/except to return a
controlled error if generation still fails).

857-857: ⚠️ Potential issue | 🟠 Major

.doc is still accepted even though processing support is not guaranteed.

Allowing .doc here can pass validation and fail later during extraction, producing avoidable user-facing errors.

🔧 Suggested fix
-        ALLOWED_EXTENSIONS = {'pdf', 'txt', 'docx', 'doc'}
+        ALLOWED_EXTENSIONS = {'pdf', 'txt', 'docx'}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/server.py` at line 857, The ALLOWED_EXTENSIONS set currently contains
'doc' which can pass early validation but later fail in extraction; update the
ALLOWED_EXTENSIONS constant (symbol: ALLOWED_EXTENSIONS) to remove 'doc' (leave
'pdf', 'txt', 'docx') so that only formats the extractor supports are allowed,
and add a short comment noting that supported formats must match the extraction
pipeline; also run/update any related validation tests that reference
ALLOWED_EXTENSIONS.
backend/Generator/mcq.py (1)

69-82: ⚠️ Potential issue | 🟡 Minor

Fallback source tracking is still incorrect, and static f-strings remain.

Line 82 still infers "sense2vec" from post-fallback length, which can mislabel fallback-generated options. Also, Lines 73–76 use f-strings without placeholders.

#!/bin/bash
rg -n 'return choices, "sense2vec" if len\(choices\) >= 3 else "fallback"|f"None of the above"|f"Incorrect option"|f"Another answer"|f"Different response"' backend/Generator/mcq.py
🔧 Suggested fix
 def get_answer_choices(answer, s2v_model):
     choices = []
+    used_sense2vec = False

     try:
         choices = find_similar_words(answer, s2v_model)
         if len(choices) > 0:
             print("Generated choices successfully for word:", answer)
-            return choices, "sense2vec"
+            used_sense2vec = len(choices) >= 3
     except Exception as e:
         print(f"Failed to generate choices for word: {answer}. Error: {e}")

     # Fallback: if sense2vec fails, generate generic distractors
     if len(choices) < 3:
         print(f"sense2vec returned {len(choices)} choices for '{answer}', adding generic fallbacks")
         fallbacks = [
             f"Not {answer}",
-            f"None of the above",
-            f"Incorrect option",
-            f"Another answer",
-            f"Different response"
+            "None of the above",
+            "Incorrect option",
+            "Another answer",
+            "Different response"
         ]
         for fb in fallbacks:
             if fb not in choices and len(choices) < 10:
                 choices.append(fb)

-    return choices, "sense2vec" if len(choices) >= 3 else "fallback"
+    return choices, "sense2vec" if used_sense2vec else "fallback"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/Generator/mcq.py` around lines 69 - 82, The code incorrectly labels
the source as "sense2vec" based on final choices length and uses unnecessary
f-strings for static texts; in the block handling choices/fallbacks, record the
source before mutating choices (e.g., set source = "sense2vec" initially and
switch to "fallback" if any fallback is appended), replace static f-strings like
f"None of the above" with plain strings, and ensure the return uses that
explicit source variable (return choices, source) so added fallbacks are
correctly tracked; reference the choices/fallbacks logic and the return
statement in this file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/Generator/nltk_utils.py`:
- Around line 14-15: safe_nltk_download currently only logs exceptions but
ignores when nltk.download(...) returns False (no exception) — update the
function safe_nltk_download to capture the return value of
nltk.download(pkg.split('/')[-1], quiet=True, raise_on_error=False), and if it
evaluates to False, log a clear error including the package name and either
retry or raise an exception so callers know the resource failed to download;
keep existing exception handling for thrown errors.

In `@backend/server.py`:
- Around line 982-999: The code currently glob()s all .vtt files and picks the
newest (subtitle_files, latest_subtitle), which can return another request's
file under concurrency; change the selection to only consider files tied to this
request (e.g., include the request's unique identifier/video_id in the filename
or use a dedicated per-request temp directory) so latest_subtitle is chosen from
a filtered set for this video; ensure the glob/filter uses subtitles_dir plus a
request-scoped pattern (or create a tempfile.mkdtemp per request and write/read
from there), keep the existing path traversal check
(os.path.abspath(...).startswith(os.path.abspath(subtitles_dir))) and continue
cleaning with clean_transcript and os.remove on the request-scoped file.
- Around line 363-376: The code filters empty options into options_with_answer
but later selects best_option = options[max_similarity_index], which can
misalign indices; instead build an explicit filtered_options list (e.g.,
filtered_options = [opt for opt in options if opt]) that corresponds to
options_with_answer[:-1] (the items used for vectorization), compute
similarities and max_similarity_index on that vector set, and then pick
best_option = filtered_options[max_similarity_index] (or preserve original
indices by storing tuples (orig_index, opt) when filtering) before appending to
outputs; update references around options_with_answer, generated_answer,
TfidfVectorizer, similarities.argmax and best_option accordingly.
- Around line 835-839: The boolean-hard flow is passing the whole question
object to make_question_harder, causing inconsistency with other endpoints;
change the call inside the loop that builds harder_questions to pass the
question string instead by calling make_question_harder(q["question"]) (ensure
this happens where generated is iterated and harder_questions is appended) so
the boolean-hard path matches sentences and multiple_choice behavior.

In `@eduaid_web/src/components/Header.jsx`:
- Around line 38-39: The isActive function currently uses
location.pathname.startsWith(path) which can mis-mark routes like
"/history-archive" as active for "/history"; update isActive to perform a
segment-aware check by returning true when location.pathname === path or when
location.pathname.startsWith(path + "/") (i.e., change the startsWith check to
startsWith(path + "/") and keep the strict equality branch) so only exact
matches or true subpaths are considered active; reference isActive and
location.pathname in Header.jsx when making the change.
- Around line 77-110: The mobile menu stays in the DOM when closed so keyboard
users can tab into links; fix by either unmounting the menu container when
closed or marking it non-interactive and linking the toggle with aria-controls:
add a stable id (e.g. "mobile-menu") to the menu div (ref=menuRef), set the
toggle button's aria-controls to that id, and then either wrap the menu
container in a conditional render ({open && <div id="mobile-menu"
...>...</div>}) to completely unmount when open is false, or if you must keep it
mounted keep the div but set aria-hidden={open ? "false" : "true"}, add inert
(or hidden) when closed and ensure link elements from navLinks receive no
tabbable state when closed (e.g. tabindex={open ? 0 : -1}); update setOpen usage
in the existing button/onClick and ensure the id matches the aria-controls
value.

---

Duplicate comments:
In `@backend/Generator/mcq.py`:
- Around line 69-82: The code incorrectly labels the source as "sense2vec" based
on final choices length and uses unnecessary f-strings for static texts; in the
block handling choices/fallbacks, record the source before mutating choices
(e.g., set source = "sense2vec" initially and switch to "fallback" if any
fallback is appended), replace static f-strings like f"None of the above" with
plain strings, and ensure the return uses that explicit source variable (return
choices, source) so added fallbacks are correctly tracked; reference the
choices/fallbacks logic and the return statement in this file.

In `@backend/server.py`:
- Around line 470-473: The boolean predictor is being invoked with the wrong
payload shape: answer.predict_boolean_answer is passed {"input_text":
input_text, "input_question": question} where input_question must be a list (the
predictor returns list-based results); change the call to pass input_question as
a list (e.g., {"input_text": input_text, "input_question": [question]}) or
otherwise match the predictor's expected list shape and then index the returned
list to produce the single "True"/"False" string; update any variable names
around answer.predict_boolean_answer and qa_response handling to reflect the
list-based response.
- Around line 829-833: The call to qg.generate in the /get_boolq_hard flow
passes answer_style="true_false", which doesn't match the generator's expected
style contract and can cause runtime failures; update the answer_style argument
in qg.generate to the exact allowed value or enum used by the generator (e.g.,
the constant or string defined in the generator implementation such as
AnswerStyle.BOOLQ or the provider's expected "boolq"/"boolean" token), and add a
pre-call validation or mapping for answer_style in the /get_boolq_hard handler
to convert/validate incoming styles before invoking qg.generate (and wrap the
call in a try/except to return a controlled error if generation still fails).
- Line 857: The ALLOWED_EXTENSIONS set currently contains 'doc' which can pass
early validation but later fail in extraction; update the ALLOWED_EXTENSIONS
constant (symbol: ALLOWED_EXTENSIONS) to remove 'doc' (leave 'pdf', 'txt',
'docx') so that only formats the extractor supports are allowed, and add a short
comment noting that supported formats must match the extraction pipeline; also
run/update any related validation tests that reference ALLOWED_EXTENSIONS.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78bf5a7 and 7f505d2.

📒 Files selected for processing (4)
  • backend/Generator/mcq.py
  • backend/Generator/nltk_utils.py
  • backend/server.py
  • eduaid_web/src/components/Header.jsx

…hard consistency, options index alignment

- Header.jsx: conditionally render mobile menu (unmount when closed) for keyboard a11y
- Header.jsx: add aria-controls/id linking between toggle and menu
- Header.jsx: fix isActive to use segment-aware check (path + '/') preventing false matches
- server.py: filter subtitle glob by video_id to prevent cross-request file pickup
- server.py: fix boolq_hard to pass question string and preserve dict structure
- server.py: fix options index misalignment when empty options are filtered out
- nltk_utils.py: log warning when nltk.download returns False
…cq source tracking, doc extension

- server.py: boolq_hard now uses BoolQGen.generate_boolq instead of qg.generate with invalid 'true_false' answer_style (valid: all/sentences/multiple_choice)
- server.py: predict_boolean_answer now receives input_question as [question] list matching expected payload shape (prevents character iteration)
- server.py: remove 'doc' from ALLOWED_EXTENSIONS (FileProcessor only supports txt/pdf/docx)
- mcq.py: track distractor source before mutation; remove f-strings from static strings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants