Skip to content

feat: Add AI-Powered Story Explanation Feature with 3D Avatar Narration#506

Open
mukul-sharma-tech wants to merge 3 commits intoAOSSIE-Org:mainfrom
mukul-sharma-tech:main
Open

feat: Add AI-Powered Story Explanation Feature with 3D Avatar Narration#506
mukul-sharma-tech wants to merge 3 commits intoAOSSIE-Org:mainfrom
mukul-sharma-tech:main

Conversation

@mukul-sharma-tech
Copy link

@mukul-sharma-tech mukul-sharma-tech commented Mar 1, 2026

feat: Add AI-Powered Story Explanation Feature with 3D Avatar Narration

🎯 Overview

This PR introduces an interactive story explanation feature that transforms educational topics into engaging narratives, presented by an animated 3D avatar with synchronized speech synthesis.

✨ What's New

  • Story Generation: AI-powered educational story creation from text input or PDF documents
  • 3D Avatar Narration: Realistic avatar with lip-sync, blinking, and natural head movements
  • Multi-language Support: English, Hindi, and Hinglish narration options
  • PDF Upload: Extract text from PDFs and generate stories automatically
  • Seamless Integration: New routes and navigation from home page

🔧 Technical Changes

Frontend (eduaid_web/)

New Components:

  • src/components/Avatar.jsx - 3D avatar with morph target animations
  • src/pages/story/ChooseTopic.jsx - Story generation interface
  • src/pages/story/StoryNarrator.jsx - Avatar narration display

Updated Files:

  • src/App.js - Added /choose-topic and /story routes
  • src/pages/Home.jsx - Added "Try Story Feature" navigation button
  • package.json - Added dependencies: framer-motion, lucide-react, @react-three/fiber, @react-three/drei, three

Backend (backend/)

New Endpoint: POST /generate_story

  • Accepts: input_text, language, use_mediawiki
  • Returns: Formatted educational story
  • Uses: Existing ShortQGen model for content generation

🎨 Features

  1. Text Input Mode: Enter any topic and generate an educational story
  2. PDF Upload Mode: Upload documents and auto-generate stories from content
  3. Language Selection: Choose between English, Hindi, or Hinglish
  4. 3D Avatar: Animated character with realistic lip-sync and facial expressions
  5. Speech Synthesis: Browser-native text-to-speech with language support
  6. Responsive Design: Works on desktop and mobile devices

📦 Dependencies Added

{
  "framer-motion": "^10.16.0",
  "lucide-react": "^0.294.0",
  "@react-three/fiber": "^8.15.0",
  "@react-three/drei": "^9.88.0",
  "three": "^0.158.0"
}

🚀 How to Test

1. Start Backend:

cd backend
python server.py

2. Start Frontend:

cd eduaid_web
npm install
npm start

3. Test Story Feature:

  • Navigate to home page
  • Click "Try Story Feature"
  • Enter a topic or upload a PDF
  • Select language preference
  • Click "Generate" and then "Proceed"
  • Watch the 3D avatar narrate the story

📝 Notes

  • Requires backend server running on http://localhost:5000
  • 3D avatar model should be placed at /public/models/human.glb (falls back to placeholder if missing)
  • Web Speech API support required for narration (Chrome, Edge, Safari recommended)

📸 Screenshots

Home Page - New Story Feature Button

image

Story Generation Interface

image

3D Avatar Narration

image

Type of Change

  • New feature
  • Bug fix
  • Breaking change
  • Documentation update

Checklist

  • My PR addresses a single issue, fixes a single bug or makes a single improvement
  • My code follows the project's code style and conventions
  • If applicable, I have made corresponding changes or additions to the documentation
  • If applicable, I have made corresponding changes or additions to tests
  • My changes generate no new warnings or errors
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • I have read the Contribution Guidelines
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments

AI Usage Disclosure

  • This PR does not contain AI-generated code at all
  • This PR contains AI-generated code. I have tested the code locally and I am responsible for it

AI Models and Tools Used:

  • Kiro AI Assistant - For code generation, integration planning, and implementation guidance
  • GitHub Copilot (if applicable) - For code suggestions and autocompletion

AI-Generated Components:

  • Initial structure of Avatar.jsx component (heavily modified with custom morph target animations)
  • Backend /generate_story endpoint implementation
  • Integration logic in ChooseTopic.jsx for API communication
  • Route configuration updates

Manual Modifications:

  • Enhanced Avatar component with lip-sync, blinking, and head sway animations
  • Custom story formatting logic in backend
  • UI/UX refinements and styling
  • Error handling and loading states
  • Integration with existing apiClient utility

Testing Performed:

  • ✅ Backend server starts without errors
  • ✅ Frontend builds successfully with no warnings
  • ✅ Story generation works with text input
  • ✅ PDF upload and text extraction functional
  • ✅ 3D avatar renders and animates correctly
  • ✅ Speech synthesis works in supported browsers
  • ✅ Navigation between routes works seamlessly
  • ✅ All existing features remain functional

⚠️ AI Notice - Important!

I acknowledge that I have used AI tools responsibly when creating this Pull Request. I have thoroughly tested all code locally, verified that it meets the task requirements, builds successfully, and generates no errors or warnings. I understand the changes I am proposing and take full responsibility for the code quality and functionality.

Summary by CodeRabbit

  • New Features

    • Story generation from text input or PDF uploads with multi-language support (English, Hindi, Hinglish)
    • Two new pages: topic chooser and story narrator with routing and a "Try Story Feature" entry on Home
    • AI avatar-powered storytelling with real-time speech narration, facial animation, and 3D scene
  • Chores

    • Added runtime dependencies for 3D graphics and animations
  • Style

    • Minor text and UI tweaks on Home (apostrophe and button icon)

@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

Warning

Rate limit exceeded

@mukul-sharma-tech has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 18 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between b6c523f and dc60f55.

📒 Files selected for processing (3)
  • backend/server.py
  • eduaid_web/src/components/Avatar.jsx
  • eduaid_web/src/pages/story/ChooseTopic.jsx
📝 Walkthrough

Walkthrough

A story generation and narration feature: backend adds POST /generate_story to assemble narratives (optional MediaWiki, ShortQGen questions); frontend adds ChooseTopic (text/PDF input) and StoryNarrator (Web Speech + 3D Avatar) pages, routing, a 3D Avatar component, new runtime dependencies, and a VS Code settings file.

Changes

Cohort / File(s) Summary
Backend Story Generation
backend/server.py
Added POST route /generate_story: validates input, optional MediaWiki processing, generates questions via ShortQGen, assembles intro/context/key-points, returns JSON {story, language}, with error handling and logging.
Frontend Story Pages
eduaid_web/src/pages/story/ChooseTopic.jsx, eduaid_web/src/pages/story/StoryNarrator.jsx
New ChooseTopic page: text entry + PDF upload, language selector, calls backend /generate_story, cleans/stores story, navigates to narrator. New StoryNarrator page: consumes story/topic/language, uses Web Speech API for narration and toggles isSpeaking to drive avatar animation.
3D Avatar Component
eduaid_web/src/components/Avatar.jsx
New Avatar React component using @react-three/fiber and @react-three/drei: loads GLB, detects head mesh and morph targets, implements viseme lip-sync, blinking, head sway, and idle animation playback; controlled by isSpeaking prop.
Routing & Landing
eduaid_web/src/App.js, eduaid_web/src/pages/Home.jsx
Registered routes /choose-topic and /story in App.js. Home.jsx updated text and added "Try Story Feature" button linking to /choose-topic with arrow icon.
Dependencies & Config
eduaid_web/package.json, .vscode/settings.json
Added runtime deps: @react-three/drei, @react-three/fiber, three, framer-motion, lucide-react. Added .vscode/settings.json (empty JSON object).

Sequence Diagram

sequenceDiagram
    participant User as User
    participant FrontendUI as Frontend (ChooseTopic)
    participant Backend as Backend API
    participant MediaWiki as MediaWiki
    participant ShortQGen as ShortQGen
    participant FrontendStory as Frontend (StoryNarrator)
    participant Avatar as Avatar Component
    participant SpeechAPI as Web Speech API

    User->>FrontendUI: Enter topic / Upload PDF
    FrontendUI->>FrontendUI: Validate / extract text
    FrontendUI->>Backend: POST /generate_story (input_text, language, use_mediawiki)
    Backend->>MediaWiki: Optional processing (use_mediawiki)
    MediaWiki-->>Backend: Processed text
    Backend->>ShortQGen: Generate short questions
    ShortQGen-->>Backend: Questions
    Backend->>Backend: Assemble story (intro, context, key points)
    Backend-->>FrontendUI: { story, language }
    FrontendUI->>User: Display Generated Story
    User->>FrontendUI: Click Proceed
    FrontendUI->>FrontendStory: Navigate with {topic, story, language}
    FrontendStory->>SpeechAPI: Synthesize speech (story, lang)
    SpeechAPI->>FrontendStory: onstart event
    FrontendStory->>Avatar: set isSpeaking = true
    Avatar->>Avatar: animate lip-sync, blink, head sway
    SpeechAPI->>FrontendStory: onend event
    FrontendStory->>Avatar: set isSpeaking = false
    Avatar->>Avatar: reset morphs / idle
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 I twitch my nose and spin a tale,
From prompts to voice and blinking scale,
A tiny hop, a 3D nod,
Stories whisper, screens applaud,
Bounce on, little code-woven trail!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% 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 and specifically describes the main feature: adding an AI-powered story explanation feature with 3D avatar narration, which is the core of 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: 5

🧹 Nitpick comments (7)
eduaid_web/src/components/Avatar.jsx (1)

25-26: Remove debug console.log statements before merging.

These logging statements are useful during development but should be removed for production to avoid cluttering the browser console.

♻️ Proposed fix
          headMesh.current = child
          const dict = child.morphTargetDictionary
-          console.log('✅ Head Mesh Found:', child.name)
-          console.log('🧠 Morph Targets:', dict)

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

In `@eduaid_web/src/components/Avatar.jsx` around lines 25 - 26, Remove the debug
console.log calls in Avatar.jsx that print the head mesh and morph targets (the
two console.log lines that output '✅ Head Mesh Found:' and '🧠 Morph Targets:');
either delete them or gate them behind an environment/debug flag (e.g., a DEBUG
constant) so they don't run in production, ensuring the logic around detecting
the head mesh and building dict (the surrounding code that references child.name
and dict) remains unchanged.
eduaid_web/src/pages/story/ChooseTopic.jsx (2)

3-3: Remove unused imports and state.

Moon and Sun are imported but never used, and darkMode state is declared but not utilized in the UI. Remove these to clean up the code.

♻️ Proposed fix
-import { Moon, Sun, ChevronRight, Upload, BookText } from 'lucide-react';
+import { ChevronRight, Upload, BookText } from 'lucide-react';
 export default function ChooseTopic() {

-  const [darkMode, setDarkMode] = useState(false);
   const [activeTab, setActiveTab] = useState('text');

Also applies to: 9-9

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

In `@eduaid_web/src/pages/story/ChooseTopic.jsx` at line 3, Remove the unused
imports Moon and Sun from the import list in ChooseTopic (remove them from the
statement that currently imports Moon, Sun, ChevronRight, Upload, BookText) and
delete the unused darkMode state declaration (and any setDarkMode if present)
inside the ChooseTopic component so there are no unused variables; keep
ChevronRight, Upload, BookText as-is and ensure no other references to Moon,
Sun, or darkMode remain in the file.

50-52: Avoid hardcoding localhost URL in user-facing error messages.

The error message exposes an internal implementation detail (localhost:5000) which is incorrect for production deployments and confusing for end users.

♻️ Proposed fix
     } catch (err) {
       console.error(err);
-      alert('Story generation failed. Please ensure the backend server is running on http://localhost:5000');
+      alert('Story generation failed. Please try again later.');
     } finally {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/story/ChooseTopic.jsx` around lines 50 - 52, The catch
block in the ChooseTopic component currently alerts a hardcoded backend URL
(http://localhost:5000); replace this user-facing message with a
non-environment-specific, friendly error (e.g., "Story generation failed —
please try again later or contact support") and keep the detailed error for
developer logs (console.error(err)). Update the error handling in the catch of
the async story generation/fetch logic inside ChooseTopic.jsx to remove the
hardcoded URL and provide a generic, production-safe alert while preserving the
original err logging for diagnostics.
.vscode/settings.json (1)

1-2: Consider removing this empty settings file.

An empty .vscode/settings.json adds no value to the repository. Either add meaningful workspace settings (e.g., editor configuration, extension recommendations) or remove this file to reduce repository noise.

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

In @.vscode/settings.json around lines 1 - 2, This .vscode/settings.json is
empty and should be removed to avoid repo noise or replaced with meaningful
workspace settings; either delete the empty file from the PR or populate
settings.json with explicit editor/workspace preferences (e.g.,
"editor.formatOnSave", "files.exclude", or extension recommendations) so it
carries value.
backend/server.py (2)

496-502: Consider adding input length validation.

There's no upper limit on input_text length. Very large inputs could cause performance issues or resource exhaustion. Consider adding a reasonable maximum length.

♻️ Proposed fix
     input_text = data.get("input_text", "")
     language = data.get("language", "english")
     use_mediawiki = data.get("use_mediawiki", 0)
     
     if not input_text:
         return jsonify({"error": "No input text provided"}), 400
+    
+    MAX_INPUT_LENGTH = 10000  # Adjust as needed
+    if len(input_text) > MAX_INPUT_LENGTH:
+        return jsonify({"error": f"Input text exceeds maximum length of {MAX_INPUT_LENGTH} characters"}), 400
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/server.py` around lines 496 - 502, Add an upper bound check for the
request payload by defining a MAX_INPUT_LENGTH (e.g. 10_000) and validating the
length of the extracted input_text (from request.get_json() and the input_text
variable). After the existing empty check, return a 400 JSON error if
len(input_text) exceeds MAX_INPUT_LENGTH with a clear message like "input_text
too long, max X characters" so the endpoint (where input_text is used) is
protected from overly large inputs.

553-555: Consider catching more specific exceptions.

Catching a broad Exception can mask unexpected errors and make debugging harder. Consider catching specific exceptions (e.g., ValueError, KeyError) that you expect from the story generation logic, and let unexpected errors propagate for proper logging/monitoring.

♻️ Proposed fix
-    except Exception as e:
-        print(f"Error generating story: {str(e)}")
-        return jsonify({"error": f"Story generation failed: {str(e)}"}), 500
+    except (ValueError, KeyError) as e:
+        print(f"Error generating story: {e!r}")
+        return jsonify({"error": f"Story generation failed: {e!s}"}), 500
+    except Exception as e:
+        # Log unexpected errors for debugging
+        print(f"Unexpected error generating story: {e!r}")
+        return jsonify({"error": "Story generation failed unexpectedly"}), 500
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/server.py` around lines 553 - 555, Replace the broad "except
Exception as e" in the story-generation error handler with targeted exception
handlers for expected failure modes (for example: "except (ValueError, KeyError)
as e" and/or any domain-specific StoryGenerationError) and return the same
jsonify error response for those cases; do not swallow unexpected
exceptions—either let them propagate or catch them and re-raise/log
appropriately (e.g., use a final "except Exception: raise" or remove the broad
catch) so unexpected errors surface to your monitoring.
eduaid_web/src/pages/story/StoryNarrator.jsx (1)

7-16: Handle direct navigation without state.

If a user navigates directly to /story (e.g., via URL or bookmark), location.state will be null, resulting in an empty page. Consider redirecting to /choose-topic or displaying a helpful message.

♻️ Proposed fix
 export default function StoryNarrator() {
   const location = useLocation();
   const navigate = useNavigate();
   const { story = '', topic = '', language = 'english' } = location.state || {};

   const [isSpeaking, setIsSpeaking] = useState(false);
   const [animation, setAnimation] = useState('Idle');

+  useEffect(() => {
+    if (!location.state || !location.state.story) {
+      navigate('/choose-topic', { replace: true });
+    }
+  }, [location.state, navigate]);
+
   useEffect(() => {
     if (!story) return;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/story/StoryNarrator.jsx` around lines 7 - 16, The
StoryNarrator component currently reads story/topic/language from location.state
which can be null when a user navigates directly; update the component to detect
a missing story (and/or topic) inside the existing useEffect (or at top of
render) and perform a programmatic redirect to the topic selection route (use
the existing navigate from useNavigate to navigate('/choose-topic')) or render a
friendly fallback message/UI if you prefer; specifically modify the logic around
location.state / the story/topic variables in StoryNarrator so when story is
falsy you call navigate('/choose-topic') (or return the fallback UI) to avoid
showing an empty page.
🤖 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/server.py`:
- Around line 516-518: The first_sentence extraction can be empty when
input_text begins with a period; update the logic that builds first_sentence
(variable first_sentence in the block that appends to story_parts) to trim
leading dots/whitespace and pick the first non-empty segment, falling back to a
safe substring (e.g., input_text[:100] after stripping) if no non-empty sentence
exists; ensure the subsequent append uses this sanitized first_sentence so you
don't produce "Let me explain ." for inputs like "." or ".Hello".

In `@eduaid_web/src/components/Avatar.jsx`:
- Around line 63-68: The setTimeout used to reset head.morphTargetInfluences for
morphs.blinkL and morphs.blinkR can fire after the Avatar component unmounts;
store the timeout ID in a ref (e.g., blinkTimeoutRef) when calling setTimeout
and clear it in the component's cleanup (useEffect return) using
clearTimeout(blinkTimeoutRef.current) and null the ref; ensure any code that
accesses head.morphTargetInfluences checks the component is still mounted (or
head exists) before touching morph indices.

In `@eduaid_web/src/pages/story/ChooseTopic.jsx`:
- Around line 19-25: The cleanText function's regex ([^\w\s.,?!]) is too
aggressive and strips useful punctuation (hyphens, apostrophes, quotes, colons,
parentheses); update the cleanText implementation to allow those characters by
expanding the allowed character class (e.g., include - ' " : () and any
preferred Unicode apostrophes) while still removing other unwanted characters,
then keep the subsequent whitespace normalization (.replace(/\s+/g, '
').trim()); modify the cleanText function accordingly so strings like "It's a
well-known fact (see: Wikipedia)" retain expected punctuation.

In `@eduaid_web/src/pages/story/StoryNarrator.jsx`:
- Around line 15-19: The useEffect currently assumes Web Speech API exists; add
a feature-detection guard before using window.speechSynthesis and
SpeechSynthesisUtterance in the useEffect that creates synth and utterance
(e.g., check window.speechSynthesis && typeof SpeechSynthesisUtterance !==
'undefined'), and handle the unsupported case by early-returning or triggering a
fallback/user notification (so modify the effect that defines synth and
utterance to bail out or call a fallback handler when the API is unavailable).
- Around line 23-25: The hinglish branch currently strips any word lacking ASCII
letters causing mixed-language content loss; update the hinglish handling so
utterance.lang is set to 'hi-IN' but do not filter words—use the original story
(optionally only remove punctuation like periods/commas) for utterance.text
instead of the map/filter that drops non-ASCII words; change the code that
assigns utterance.text in the hinglish branch (the line that currently calls
.replace(...).split(' ').map(...).join(' ')) to preserve all words.

---

Nitpick comments:
In @.vscode/settings.json:
- Around line 1-2: This .vscode/settings.json is empty and should be removed to
avoid repo noise or replaced with meaningful workspace settings; either delete
the empty file from the PR or populate settings.json with explicit
editor/workspace preferences (e.g., "editor.formatOnSave", "files.exclude", or
extension recommendations) so it carries value.

In `@backend/server.py`:
- Around line 496-502: Add an upper bound check for the request payload by
defining a MAX_INPUT_LENGTH (e.g. 10_000) and validating the length of the
extracted input_text (from request.get_json() and the input_text variable).
After the existing empty check, return a 400 JSON error if len(input_text)
exceeds MAX_INPUT_LENGTH with a clear message like "input_text too long, max X
characters" so the endpoint (where input_text is used) is protected from overly
large inputs.
- Around line 553-555: Replace the broad "except Exception as e" in the
story-generation error handler with targeted exception handlers for expected
failure modes (for example: "except (ValueError, KeyError) as e" and/or any
domain-specific StoryGenerationError) and return the same jsonify error response
for those cases; do not swallow unexpected exceptions—either let them propagate
or catch them and re-raise/log appropriately (e.g., use a final "except
Exception: raise" or remove the broad catch) so unexpected errors surface to
your monitoring.

In `@eduaid_web/src/components/Avatar.jsx`:
- Around line 25-26: Remove the debug console.log calls in Avatar.jsx that print
the head mesh and morph targets (the two console.log lines that output '✅ Head
Mesh Found:' and '🧠 Morph Targets:'); either delete them or gate them behind an
environment/debug flag (e.g., a DEBUG constant) so they don't run in production,
ensuring the logic around detecting the head mesh and building dict (the
surrounding code that references child.name and dict) remains unchanged.

In `@eduaid_web/src/pages/story/ChooseTopic.jsx`:
- Line 3: Remove the unused imports Moon and Sun from the import list in
ChooseTopic (remove them from the statement that currently imports Moon, Sun,
ChevronRight, Upload, BookText) and delete the unused darkMode state declaration
(and any setDarkMode if present) inside the ChooseTopic component so there are
no unused variables; keep ChevronRight, Upload, BookText as-is and ensure no
other references to Moon, Sun, or darkMode remain in the file.
- Around line 50-52: The catch block in the ChooseTopic component currently
alerts a hardcoded backend URL (http://localhost:5000); replace this user-facing
message with a non-environment-specific, friendly error (e.g., "Story generation
failed — please try again later or contact support") and keep the detailed error
for developer logs (console.error(err)). Update the error handling in the catch
of the async story generation/fetch logic inside ChooseTopic.jsx to remove the
hardcoded URL and provide a generic, production-safe alert while preserving the
original err logging for diagnostics.

In `@eduaid_web/src/pages/story/StoryNarrator.jsx`:
- Around line 7-16: The StoryNarrator component currently reads
story/topic/language from location.state which can be null when a user navigates
directly; update the component to detect a missing story (and/or topic) inside
the existing useEffect (or at top of render) and perform a programmatic redirect
to the topic selection route (use the existing navigate from useNavigate to
navigate('/choose-topic')) or render a friendly fallback message/UI if you
prefer; specifically modify the logic around location.state / the story/topic
variables in StoryNarrator so when story is falsy you call
navigate('/choose-topic') (or return the fallback UI) to avoid showing an empty
page.

ℹ️ 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 dde0e60.

⛔ Files ignored due to path filters (1)
  • eduaid_web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • .vscode/settings.json
  • backend/server.py
  • eduaid_web/package.json
  • eduaid_web/public/models/human.glb
  • eduaid_web/src/App.js
  • eduaid_web/src/components/Avatar.jsx
  • eduaid_web/src/pages/Home.jsx
  • eduaid_web/src/pages/story/ChooseTopic.jsx
  • eduaid_web/src/pages/story/StoryNarrator.jsx

@mukul-sharma-tech
Copy link
Author

Thanks for review! I will do the changes suggested

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: 5

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

517-520: ⚠️ Potential issue | 🟡 Minor

first_sentence fallback still mishandles leading-dot input.

For inputs like . or .Hello, the current fallback can still produce awkward intros (e.g., Let me explain ..). This is the same edge case class previously flagged.

Suggested fix
-        first_sentence = input_text.split('.')[0].strip() if '.' in input_text else input_text[:100]
-        if not first_sentence:
-            first_sentence = input_text[:100].strip() or "this topic"
+        segments = [seg.strip() for seg in input_text.split('.') if seg.strip()]
+        first_sentence = segments[0] if segments else input_text.lstrip('. ').strip()[:100]
+        if not first_sentence:
+            first_sentence = "this topic"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/server.py` around lines 517 - 520, The computation of first_sentence
mishandles inputs starting with dots (e.g., "." or ".Hello"), so sanitize
input_text before extracting the first sentence: create a sanitized variable
(e.g., sanitized = input_text.lstrip('. ').strip()) and use
sanitized.split('.')[0] if '.' in sanitized else sanitized[:100]; then fall back
to sanitized[:100].strip() or "this topic" for empty results, and use that
sanitized first_sentence when calling story_parts.append; update the logic
around the existing first_sentence assignment and the story_parts.append call to
avoid generating leading/trailing dots in the output.
eduaid_web/src/pages/story/ChooseTopic.jsx (1)

20-24: ⚠️ Potential issue | 🟠 Major

cleanText removes non-Latin text and breaks Hindi/Hinglish stories.

Line 22 relies on \w, which strips Devanagari and other scripts. That conflicts with the language selector (Lines 155-157).

Suggested fix
   const cleanText = (text) => {
     return text
-      .replace(/[^\w\s.,?!:;'"\-()]/g, '') // preserve common punctuation
+      .replace(/[^\p{L}\p{N}\s.,?!:;'"\-()]/gu, '') // keep letters/numbers across languages
       .replace(/\s+/g, ' ')
       .trim();
   };
What is browser support for JavaScript RegExp Unicode property escapes (`\p{L}`, `\p{N}` with the `u` flag), especially for React apps targeting modern Chrome, Firefox, and Safari?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eduaid_web/src/pages/story/ChooseTopic.jsx` around lines 20 - 24, The
cleanText function is currently using \w which strips non-Latin scripts
(breaking Hindi/Hinglish); update the character-class to use Unicode property
escapes (e.g., allow \p{L} and \p{N} with the u flag) so letters and numbers
from Devanagari and other scripts are preserved, keep the allowed punctuation
(.,?!:;'"-()) and whitespace handling, and ensure the regex uses the global +
unicode flags (or add a graceful fallback if the runtime lacks Unicode property
escape support); locate and modify the cleanText arrow function to replace the
offending regex and retain the subsequent .replace(/\s+/g, ' ').trim() logic.
eduaid_web/src/pages/story/StoryNarrator.jsx (1)

18-24: ⚠️ Potential issue | 🟡 Minor

Add constructor feature detection for SpeechSynthesisUtterance.

Line 18 guards speechSynthesis, but Line 24 still assumes SpeechSynthesisUtterance exists. Some browsers expose one without fully supporting the other.

Suggested fix
-    if (!window.speechSynthesis) {
+    if (!window.speechSynthesis || typeof SpeechSynthesisUtterance === 'undefined') {
       console.warn('Web Speech API not supported');
       return;
     }
In current browser compatibility docs, can `window.speechSynthesis` exist while `SpeechSynthesisUtterance` is unavailable or partially unsupported? Please include support notes for Chrome, Safari (iOS/macOS), and Firefox.
🤖 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/server.py`:
- Around line 555-557: The except block that prints and returns the raw
exception (the print statement and the jsonify response in the except Exception
as e handler) is leaking internal error details to clients; update the handler
to log the full exception internally using an application logger (e.g.,
logger.exception or app.logger.error with the exception) and change the API
response (the jsonify call) to return a generic error message such as "Story
generation failed" without including str(e) or other internals, keeping the HTTP
500 status; ensure you remove or replace the print(f"Error generating story:
{str(e)}") with the internal logging call.
- Around line 496-503: The handler calls request.get_json() and immediately uses
data.get which will raise if get_json() returned None; update the input
validation to guard for non-JSON or None payloads by checking that data is
truthy before accessing keys (i.e., verify data is not None), and if it is None
return a 400 JSON response like {"error":"Invalid or missing JSON payload"}
instead of proceeding; update the block around request.get_json(), data,
input_text, language, and use_mediawiki to perform this None check first and
then extract data.get("input_text", "") etc.

In `@eduaid_web/src/components/Avatar.jsx`:
- Around line 52-53: The frame loop currently bails out whenever
morphs.mouthOpen is null (if (!head || morphs.mouthOpen == null) return), which
prevents blink/head-sway from running; change the guard to only return when head
is missing (if (!head) return) or when neither mouth nor blink morphs exist
(e.g., if (!head || (!hasMouthMorph && !hasBlinkMorph)) return), then move the
mouthOpen usage into the mouth-specific branch so mouth code checks
morphs.mouthOpen before using it while blink/head-sway logic only depends on
blink morph presence (reference the head variable and morphs.mouthOpen and your
blink morph keys in Avatar.jsx).
- Line 97: The forEach callback currently uses an expression arrow `(a) =>
a.stop()` which creates an implicit return flagged by Biome; change the callback
on the Object.values(actions).forEach call to use a statement block so the
callback explicitly calls `a.stop()` without returning a value (e.g., convert to
a block arrow or a regular function) to satisfy the useIterableCallbackReturn
rule and keep the call site (`Object.values(actions).forEach`, `actions`,
`stop`) intact.

In `@eduaid_web/src/pages/story/ChooseTopic.jsx`:
- Around line 85-87: The component sets topicText to the entire extracted PDF
content via setTopicText(extractedText) and then places that full text into the
route navigation state as `topic`, which bloats history; change the
navigation/state payload to include a trimmed title or preview instead of the
full text (e.g., derive a topicTitle/topicPreview from topicText by taking the
first N characters or generating a concise title) and pass that smaller string
in the navigation call (where you currently pass topicText), while keeping the
full topicText only in component state if needed for display or processing in
ChooseTopic.jsx.

---

Duplicate comments:
In `@backend/server.py`:
- Around line 517-520: The computation of first_sentence mishandles inputs
starting with dots (e.g., "." or ".Hello"), so sanitize input_text before
extracting the first sentence: create a sanitized variable (e.g., sanitized =
input_text.lstrip('. ').strip()) and use sanitized.split('.')[0] if '.' in
sanitized else sanitized[:100]; then fall back to sanitized[:100].strip() or
"this topic" for empty results, and use that sanitized first_sentence when
calling story_parts.append; update the logic around the existing first_sentence
assignment and the story_parts.append call to avoid generating leading/trailing
dots in the output.

In `@eduaid_web/src/pages/story/ChooseTopic.jsx`:
- Around line 20-24: The cleanText function is currently using \w which strips
non-Latin scripts (breaking Hindi/Hinglish); update the character-class to use
Unicode property escapes (e.g., allow \p{L} and \p{N} with the u flag) so
letters and numbers from Devanagari and other scripts are preserved, keep the
allowed punctuation (.,?!:;'"-()) and whitespace handling, and ensure the regex
uses the global + unicode flags (or add a graceful fallback if the runtime lacks
Unicode property escape support); locate and modify the cleanText arrow function
to replace the offending regex and retain the subsequent .replace(/\s+/g, '
').trim() logic.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dde0e60 and b6c523f.

📒 Files selected for processing (4)
  • backend/server.py
  • eduaid_web/src/components/Avatar.jsx
  • eduaid_web/src/pages/story/ChooseTopic.jsx
  • eduaid_web/src/pages/story/StoryNarrator.jsx

Comment on lines +496 to +503
data = request.get_json()
input_text = data.get("input_text", "")
language = data.get("language", "english")
use_mediawiki = data.get("use_mediawiki", 0)

if not input_text:
return jsonify({"error": "No input text provided"}), 400

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard non-JSON payloads before using data.get.

On Line 496, request.get_json() can return None; Line 497 then raises and falls into a 500 path instead of returning a 400 validation error.

Suggested fix
-    data = request.get_json()
-    input_text = data.get("input_text", "")
+    data = request.get_json(silent=True) or {}
+    input_text = (data.get("input_text") or "").strip()
     language = data.get("language", "english")
     use_mediawiki = data.get("use_mediawiki", 0)
     
     if not input_text:
         return jsonify({"error": "No input text provided"}), 400
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data = request.get_json()
input_text = data.get("input_text", "")
language = data.get("language", "english")
use_mediawiki = data.get("use_mediawiki", 0)
if not input_text:
return jsonify({"error": "No input text provided"}), 400
data = request.get_json(silent=True) or {}
input_text = (data.get("input_text") or "").strip()
language = data.get("language", "english")
use_mediawiki = data.get("use_mediawiki", 0)
if not input_text:
return jsonify({"error": "No input text provided"}), 400
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/server.py` around lines 496 - 503, The handler calls
request.get_json() and immediately uses data.get which will raise if get_json()
returned None; update the input validation to guard for non-JSON or None
payloads by checking that data is truthy before accessing keys (i.e., verify
data is not None), and if it is None return a 400 JSON response like
{"error":"Invalid or missing JSON payload"} instead of proceeding; update the
block around request.get_json(), data, input_text, language, and use_mediawiki
to perform this None check first and then extract data.get("input_text", "")
etc.

@mukul-sharma-tech
Copy link
Author

@coderabbitai Thanks for the thorough review! I've addressed all the issues

@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

Tip

For best results, initiate chat on the files or code changes.

@mukul-sharma-tech, thank you for addressing all the feedback! I appreciate your responsiveness and commitment to improving the code quality. The story explanation feature with 3D avatar narration looks like an exciting addition to EduAid! 🎉

If you'd like me to review the changes, feel free to ask and I'll take another look at the updated code.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@mukul-sharma-tech
Copy link
Author

mukul-sharma-tech commented Mar 1, 2026

@coderabbitai when will u merge it

@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

Tip

For best results, initiate chat on the files or code changes.

@mukul-sharma-tech, I appreciate your enthusiasm! However, I should clarify that I'm a code review assistant and don't have merge permissions. I can only provide automated code reviews and suggestions to help improve your code.

Only repository maintainers with write access can merge pull requests. Since this is the AOSSIE-Org/EduAid repository, you'll need to wait for one of the maintainers to:

  1. Review your changes
  2. Approve the PR
  3. Merge it into the main branch

Since you've already addressed the review feedback, your PR is ready for human review. The maintainers will merge it when they have a chance to review and approve your work. If you'd like to speed up the process, you could:

  • Check if there are specific maintainers you should notify
  • Ensure all CI/CD checks are passing
  • Make sure the PR description clearly explains the changes

Good luck with your PR! 🚀

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.

1 participant