diff --git a/README.md b/README.md new file mode 100644 index 0000000..94994c3 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# AI-Tutor +AI-Tutor for Seattle DataAI Project + + +## Contributors + +[@ahumblefool](https://github.com/ahumblefool) +[@utopianvision](https://github.com/utopianvision) + [@MeghaN28](https://github.com/MeghaN28) + [@Neharor](https://github.com/Neharor) diff --git a/backend/app.py b/backend/app.py index 3057347..fe6fa79 100644 --- a/backend/app.py +++ b/backend/app.py @@ -2,19 +2,26 @@ from flask_cors import CORS import os from dotenv import load_dotenv -from services import tutor_session_service # Import the tutor session service module -from services import quiz_service # Import the quiz service module +from services import tutor_session_service # AI Tutoring +from services import quiz_service # Quiz System +from services import student_card_service # Student Card Service +import google.generativeai as genai # Load API key from .env file load_dotenv() -import google.generativeai as genai genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) # Initialize Flask app app = Flask(__name__) CORS(app) # Allow frontend access -# Routes for Tutor Session +# === AI Model for Answer Validation === +AI_VALIDATOR = genai.GenerativeModel('gemini-1.5-flash') + +# In-memory storage for Peer Learning (Replace with a database in production) +peer_questions = [] + +# === Tutor Session Routes === @app.route("/upload", methods=["POST"]) def upload(): return tutor_session_service.upload_file(request) @@ -31,46 +38,107 @@ def stream(): def index(): return jsonify({"message": "Tutoring system is running"}) -# Routes for Quiz +# === Student Card Routes === +@app.route("/student/", methods=["GET"]) +def get_student(student_id): + return student_card_service.get_student(student_id) + +@app.route("/student//feedback", methods=["POST"]) +def add_feedback(student_id): + return student_card_service.add_feedback(student_id, request) + +@app.route("/student//progress", methods=["GET"]) +def get_student_progress(): + return student_card_service.get_progress(request) + +@app.route("/api/student-insights", methods=["POST"]) +def student_insights(): + return student_card_service.get_student_insights(request) + +# === Quiz Routes === @app.route("/quiz/subjects", methods=["GET"]) def get_subjects(): - """Returns a list of available quiz subjects.""" return quiz_service.get_subjects() @app.route("/quiz", methods=["GET"]) def generate_quiz(): - """Generates 5 quiz questions for the selected subject.""" return quiz_service.generate_quiz(request) @app.route("/quiz/submit", methods=["POST"]) def submit_quiz(): - """Validates a user's answer and updates their score.""" return quiz_service.submit_quiz(request) @app.route("/quiz/stream_feedback", methods=["POST"]) def stream_feedback(): - """Streams AI-generated detailed feedback on the answer.""" return quiz_service.stream_feedback(request) @app.route("/quiz/progress", methods=["GET"]) -def get_progress(): - """Returns the user's quiz progress, including score and number of quizzes attempted.""" +def get_quiz_progress(): return quiz_service.get_progress() @app.route("/quiz/leaderboard", methods=["GET"]) def get_leaderboard(): - """Returns the leaderboard of top players.""" return quiz_service.get_leaderboard() @app.route("/quiz/badges", methods=["GET"]) def get_badges(): - """Returns the badges earned by the user based on their quiz progress.""" return quiz_service.get_badges() @app.route("/quiz/retry", methods=["POST"]) def retry_quiz(): - """Allows the user to retry the quiz for a selected subject.""" return quiz_service.retry_quiz(request) +# === Peer Learning Routes === +@app.route('/ask_question', methods=['POST']) +def ask_question(): + """Students can post questions""" + data = request.json + question = data.get("question", "").strip() + student_name = data.get("student_name", "Anonymous") + + if not question: + return jsonify(success=False, message="Question cannot be empty") + + question_entry = { + "question": question, + "student": student_name, + "answers": [] + } + + peer_questions.append(question_entry) + return jsonify(success=True, message="Question posted successfully") + +@app.route('/answer_question', methods=['POST']) +def answer_question(): + """Students can answer posted questions""" + data = request.json + question_text = data.get("question", "").strip() + answer = data.get("answer", "").strip() + student_name = data.get("student_name", "Anonymous") + + if not question_text or not answer: + return jsonify(success=False, message="Both question and answer are required") + + for question_entry in peer_questions: + if question_entry["question"] == question_text: + # AI validation of the answer + ai_feedback = AI_VALIDATOR.generate_content( + f"Validate Provide your answer: {answer}" + ) + validated_answer = { + "student": student_name, + "answer": answer, + "ai_feedback": ai_feedback.text + } + question_entry["answers"].append(validated_answer) + return jsonify(success=True, message="Answer submitted successfully with AI feedback") + + return jsonify(success=False, message="Question not found") + +@app.route('/get_questions', methods=['GET']) +def get_questions(): + """Fetch all posted questions with answers""" + return jsonify(success=True, questions=peer_questions) + if __name__ == "__main__": app.run(debug=True) diff --git a/backend/services/peer_learning_service.py b/backend/services/peer_learning_service.py index e69de29..a203491 100644 --- a/backend/services/peer_learning_service.py +++ b/backend/services/peer_learning_service.py @@ -0,0 +1,64 @@ +from flask import Flask, request, jsonify +import google.generativeai as genai + +app = Flask(__name__) + +# AI Model for Validation +AI_VALIDATOR = genai.GenerativeModel('gemini-1.5-flash') + +# In-memory storage (Replace with a database in production) +peer_questions = [] + +@app.route('/ask_question', methods=['POST']) +def ask_question(): + """Students can post questions""" + data = request.json + question = data.get("question", "").strip() + student_name = data.get("student_name", "Anonymous") + + if not question: + return jsonify(success=False, message="Question cannot be empty") + + question_entry = { + "question": question, + "student": student_name, + "answers": [] + } + + peer_questions.append(question_entry) + return jsonify(success=True, message="Question posted successfully") + +@app.route('/answer_question', methods=['POST']) +def answer_question(): + """Students can answer posted questions""" + data = request.json + question_text = data.get("question", "").strip() + answer = data.get("answer", "").strip() + student_name = data.get("student_name", "Anonymous") + + if not question_text or not answer: + return jsonify(success=False, message="Both question and answer are required") + + for question_entry in peer_questions: + if question_entry["question"] == question_text: + # AI validation of the answer + ai_feedback = AI_VALIDATOR.generate_content( + f"Validate the following answer and provide improvement suggestions: {answer}" + ) + validated_answer = { + "student": student_name, + "answer": answer, + "ai_feedback": ai_feedback.text + } + question_entry["answers"].append(validated_answer) + return jsonify(success=True, message="Answer submitted successfully with AI feedback") + + return jsonify(success=False, message="Question not found") + +@app.route('/get_questions', methods=['GET']) +def get_questions(): + """Fetch all posted questions with answers""" + return jsonify(success=True, questions=peer_questions) + +if __name__ == '__main__': + app.run(debug=True) diff --git a/backend/services/quiz_service.py b/backend/services/quiz_service.py index 7dacb0d..806831a 100644 --- a/backend/services/quiz_service.py +++ b/backend/services/quiz_service.py @@ -149,3 +149,4 @@ def calculate_score(answers, correct_answers, quiz_data): score += 1 # Increment score for each correct answer return score + diff --git a/backend/services/student_card_service.py b/backend/services/student_card_service.py index e69de29..68fc0b2 100644 --- a/backend/services/student_card_service.py +++ b/backend/services/student_card_service.py @@ -0,0 +1,113 @@ +from flask import request, jsonify +import google.generativeai as genai +import os + +# Dummy student database (Replace with a real database in production) +students = { + "123": { + "studentId": "123", + "name": "Alice", + "subjects": { + "math": 80, + "science": 75, + "history": 85 + }, + "feedback": [ + { + "subject": "math", + "comment": "Great progress in algebra!", + "timestamp": "2025-04-01T10:00:00Z" + }, + { + "subject": "science", + "comment": "Needs more practice with experiments.", + "timestamp": "2025-04-02T14:00:00Z" + } + ], + "recentActivity": "Completed algebra problems", + "lastUpdated": "2025-04-03T09:00:00Z" + } +} + +# Configure Gemini AI +AI_MODEL = genai.GenerativeModel('gemini-1.5-flash') + +def get_student(student_id): + """Fetch student details.""" + student = students.get(student_id) + if student: + return jsonify(student) + return jsonify({"error": "Student not found"}), 404 + +def add_feedback(student_id, request): + """Add feedback for a student.""" + student = students.get(student_id) + if not student: + return jsonify({"error": "Student not found"}), 404 + + feedback = request.json.get("feedback", "") + if not feedback: + return jsonify({"error": "Feedback cannot be empty"}), 400 + + student["feedback"].append(feedback) + return jsonify({"success": True, "message": "Feedback added"}) + +def get_progress(student_id): + """Retrieve a student's academic progress.""" + student = students.get(student_id) + if not student: + return jsonify({"error": "Student not found"}), 404 + + return jsonify({"name": student["name"], "progress": student["subjects"]}) + +def get_gemini_feedback(student, subject, recent_activity): + """Generate AI feedback based on student performance and activity.""" + prompt = f""" + The student {student['name']} has been learning {subject}. + Their recent activity: {recent_activity}. + Current scores: {student['subjects']} in subjects. + Provide personalized feedback and improvement suggestions. + """ + try: + response = AI_MODEL.generate_content(prompt) + return response.text if response else "No AI feedback available." + except Exception as e: + return f"Error fetching feedback: {str(e)}" + +def get_interdisciplinary_suggestions(subject, recent_activity): + """Generate interdisciplinary suggestions dynamically using Gemini AI.""" + prompt = f""" + The student is learning {subject} and recently engaged in {recent_activity}. + Suggest relevant interdisciplinary connections to enhance their learning. + """ + try: + response = AI_MODEL.generate_content(prompt) + return response.text if response else "No interdisciplinary insights available." + except Exception as e: + return f"Error fetching suggestions: {str(e)}" + +def get_student_insights(request): + """Fetch AI-generated student insights, feedback, and interdisciplinary suggestions.""" + data = request.get_json() + + print("Received Data:", data) # Debugging + + student_id = str(data.get('studentId')) + subject = data.get('subject') + recent_activity = data.get('recentActivity') + + if not student_id or not subject or not recent_activity: + return jsonify({"error": "Student ID, subject, and recent activity are required"}), 400 + + student = students.get(student_id) + if not student: + return jsonify({"error": "Student not found"}), 404 + + # Get AI-generated feedback and interdisciplinary suggestions + ai_feedback = get_gemini_feedback(student, subject, recent_activity) + interdisciplinary_suggestions = get_interdisciplinary_suggestions(subject, recent_activity) + + return jsonify({ + "aiFeedback": ai_feedback, + "interdisciplinarySuggestions": interdisciplinary_suggestions + }) diff --git a/frontend/public/images/aitutor.jpg b/frontend/public/images/aitutor.jpg new file mode 100644 index 0000000..cf32237 Binary files /dev/null and b/frontend/public/images/aitutor.jpg differ diff --git a/frontend/src/App.js b/frontend/src/App.js index 34221e8..c41d7dd 100755 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -2,19 +2,31 @@ import React from "react"; import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import NavBar from "./NavBar"; import TutorSession from "./TutorSession/TutorSession"; +import StudentCard from "./StudentCard/StudentCard"; import Quiz from "./Quiz/Quiz"; +import PeerLearning from "./PeerLearning/PeerLearning"; +import Home from "./Home/Home"; +import Instructor from "./Instructor/Instructor"; function App() { + // Dummy student data + const studentData = { + name: "Alice", + subject: "math", + progress: 80, + recentActivity: "Completed chapter on Algebra", + }; + return ( - Home Page} /> - Instructor Page} /> - Peer Learning Page} /> - {/* Use key based on the current location */} + } /> + } /> + } /> + {/* Pass studentData as a prop to StudentCard */} + } /> } /> - Student Card Page} /> } /> diff --git a/frontend/src/Home/Home.css b/frontend/src/Home/Home.css index e69de29..5f951aa 100644 --- a/frontend/src/Home/Home.css +++ b/frontend/src/Home/Home.css @@ -0,0 +1,64 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: 'Arial', sans-serif; + } + + .home-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + background-color: white; + border-radius: 10px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + width: 80%; + max-width: 1000px; + margin: 0 auto; + } + + .welcome-message { + flex: 1; + padding-right: 20px; + } + + .welcome-message h1 { + font-size: 36px; + color: #2a2a2a; + margin-bottom: 20px; + } + + .welcome-message p { + font-size: 18px; + color: #555; + margin-bottom: 20px; + } + + .start-btn { + display: inline-block; + background-color: #4CAF50; + color: white; + padding: 15px 30px; + font-size: 18px; + text-decoration: none; + border-radius: 5px; + transition: background-color 0.3s ease; + } + + .start-btn:hover { + background-color: #45a049; + } + + .image-container { + flex: 1; + display: flex; + justify-content: center; + } + + .tutor-image { + max-width: 100%; + border-radius: 10px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + } + \ No newline at end of file diff --git a/frontend/src/Home/Home.js b/frontend/src/Home/Home.js index e69de29..f344a26 100644 --- a/frontend/src/Home/Home.js +++ b/frontend/src/Home/Home.js @@ -0,0 +1,19 @@ +import React from 'react'; +import './Home.css'; + +const Home = () => { + return ( +
+
+

Welcome to the AI-powered Tutor System

+

Your personalized learning assistant, ready to help you in Math, Science, History, and more!

+ Start Learning +
+
+ AI Tutor +
+
+ ); +}; + +export default Home; diff --git a/frontend/src/Instructor/Instructor.css b/frontend/src/Instructor/Instructor.css index e69de29..4d9b4c8 100644 --- a/frontend/src/Instructor/Instructor.css +++ b/frontend/src/Instructor/Instructor.css @@ -0,0 +1,38 @@ +.instructor-container { + text-align: center; + padding: 20px; + font-family: Arial, sans-serif; + } + + h1 { + font-size: 2rem; + color: #333; + margin-bottom: 20px; + } + + .instructor-list { + list-style: none; + padding: 0; + max-width: 400px; + margin: 0 auto; + } + + .instructor-item { + background: #f4f4f4; + padding: 15px; + margin: 10px 0; + border-radius: 8px; + box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); + } + + h2 { + margin: 0; + color: #007bff; + } + + p { + margin: 5px 0 0; + font-size: 1.2rem; + color: #555; + } + \ No newline at end of file diff --git a/frontend/src/Instructor/Instructor.js b/frontend/src/Instructor/Instructor.js index e69de29..b84e1ee 100644 --- a/frontend/src/Instructor/Instructor.js +++ b/frontend/src/Instructor/Instructor.js @@ -0,0 +1,26 @@ +import React from 'react'; +import './Instructor.css'; + +const instructors = [ + { subject: "Math", name: "Dr. Alan Turing" }, + { subject: "Science", name: "Dr. Marie Curie" }, + { subject: "History", name: "Prof. Yuval Noah Harari" } +]; + +const Instructor = () => { + return ( +
+

Meet Our Instructors

+
    + {instructors.map((instructor, index) => ( +
  • +

    {instructor.subject}

    +

    {instructor.name}

    +
  • + ))} +
+
+ ); +}; + +export default Instructor; diff --git a/frontend/src/PeerLearning/PeerLearning.css b/frontend/src/PeerLearning/PeerLearning.css index e69de29..cbe205f 100644 --- a/frontend/src/PeerLearning/PeerLearning.css +++ b/frontend/src/PeerLearning/PeerLearning.css @@ -0,0 +1,36 @@ +.peer-learning-container { + max-width: 800px; + margin: auto; + text-align: center; + } + + .ask-section input, + .questions-section input { + width: 70%; + padding: 10px; + margin-right: 10px; + } + + .ask-section button, + .questions-section button { + padding: 10px; + background-color: #007bff; + color: white; + border: none; + cursor: pointer; + } + + .question-card { + background: #f9f9f9; + padding: 15px; + margin: 10px 0; + border-radius: 5px; + } + + .answer { + background: #e3f2fd; + padding: 10px; + margin-top: 5px; + border-radius: 5px; + } + \ No newline at end of file diff --git a/frontend/src/PeerLearning/PeerLearning.js b/frontend/src/PeerLearning/PeerLearning.js index e69de29..8503fc3 100644 --- a/frontend/src/PeerLearning/PeerLearning.js +++ b/frontend/src/PeerLearning/PeerLearning.js @@ -0,0 +1,96 @@ +import React, { useEffect, useState } from "react"; +import "./PeerLearning.css"; + +const PeerLearning = () => { + const [questions, setQuestions] = useState([]); + const [newQuestion, setNewQuestion] = useState(""); + const [newAnswer, setNewAnswer] = useState({ question: "", answer: "" }); + + // Fetch questions from backend + const fetchQuestions = () => { + fetch("http://localhost:5000/get_questions") + .then((res) => res.json()) + .then((data) => { + if (data.success) setQuestions(data.questions); + }); + }; + + useEffect(() => { + fetchQuestions(); + }, []); + + // Post a new question + const askQuestion = () => { + fetch("http://localhost:5000/ask_question", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ question: newQuestion, student_name: "Student1" }), + }) + .then((res) => res.json()) + .then(() => { + setNewQuestion(""); + fetchQuestions(); + }); + }; + + // Answer an existing question + const answerQuestion = (questionText) => { + fetch("http://localhost:5000/answer_question", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + question: questionText, + answer: newAnswer.answer, + student_name: "Student2" + }), + }) + .then((res) => res.json()) + .then(() => { + setNewAnswer({ question: "", answer: "" }); + fetchQuestions(); + }); + }; + + return ( +
+

Peer Learning

+ + {/* Ask a Question Section */} +
+ setNewQuestion(e.target.value)} + /> + +
+ + {/* List of Questions */} +
+ {questions.map((q, index) => ( +
+

{q.student} asked: {q.question}

+
+ {q.answers.map((a, i) => ( +
+

{a.student} answered: {a.answer}

+

AI Feedback: {a.ai_feedback}

+
+ ))} +
+ setNewAnswer({ question: q.question, answer: e.target.value })} + /> + +
+ ))} +
+
+ ); +}; + +export default PeerLearning; diff --git a/frontend/src/Quiz/Quiz.css b/frontend/src/Quiz/Quiz.css index 46965e3..b5b341e 100644 --- a/frontend/src/Quiz/Quiz.css +++ b/frontend/src/Quiz/Quiz.css @@ -1,61 +1,63 @@ /* Overall container styling */ .quiz-container { padding: 30px; - background-color: #fdfdfd; + background-color: #1a3c5b; /* Dark Blue */ border-radius: 12px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); width: 60%; max-width: 800px; margin: 40px auto; font-family: 'Roboto', sans-serif; - } - - /* Header styling */ - .quiz-container h1 { + color: #eceff1; /* Light Gray text for better readability */ +} + +/* Header styling */ +.quiz-container h1 { text-align: center; font-size: 2.5rem; font-weight: 600; - color: #333; + color: #f4e36b; /* Soft Yellow for a warm highlight */ margin-bottom: 30px; letter-spacing: 0.5px; - } - - /* Subject selection container */ - .subject-selection { +} + +/* Subject selection container */ +.subject-selection { display: flex; flex-direction: column; align-items: center; margin-bottom: 25px; - } - - .subject-selection label { - font-size: 1.2rem; +} + +.subject-selection label { + font-size: 1.3rem; font-weight: 500; - color: #444; + color: #f4e36b; /* Soft Yellow */ margin-bottom: 10px; - } - - .subject-selection select { +} + +.subject-selection select { padding: 12px 20px; font-size: 1.1rem; border-radius: 8px; - border: 1px solid #ddd; + border: 1px solid #f4e36b; width: 70%; - background-color: #fff; - color: #333; + background-color: #2c4a73; /* Muted Dark Blue */ + color: #eceff1; /* Light Gray text */ font-weight: 500; transition: border-color 0.3s ease; - } - - .subject-selection select:focus { - border-color: #4CAF50; +} + +.subject-selection select:focus { + border-color: #f4e36b; outline: none; - } - - .start-button { +} + +/* Start Quiz Button */ +.start-button { padding: 12px 30px; - background-color: #4CAF50; - color: white; + background-color: #f57c00; /* Muted Orange */ + color: #ffffff; border: none; border-radius: 8px; cursor: pointer; @@ -63,68 +65,69 @@ font-weight: 600; transition: background-color 0.3s ease; margin-top: 15px; - } - - .start-button:hover { - background-color: #45a049; - } - - /* Loading and error message */ - .loading { +} + +.start-button:hover { + background-color: #f57c00; +} + +/* Loading and error message */ +.loading { text-align: center; font-size: 1.2rem; - color: #007BFF; - } - - .error { + color: #64b5f6; /* Light Blue */ +} + +.error { text-align: center; font-size: 1.1rem; - color: #FF5733; - } - - /* Quiz form styles */ - .quiz-form { - background-color: #fff; + color: #ff5252; /* Error Red */ +} + +/* Quiz form styles */ +.quiz-form { + background-color: #2c4a73; /* Muted Dark Blue */ padding: 25px; border-radius: 10px; - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); - } - - .question-block { + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); +} + +.question-block { margin-bottom: 25px; - } - - .question-block h3 { +} + +.question-block h3 { font-size: 1.4rem; - color: #333; + color: #f4e36b; /* Soft Yellow */ font-weight: 500; - } - - .option { +} + +.option { display: flex; align-items: center; padding: 12px; border-radius: 8px; - background-color: #f7f7f7; + background-color: #2c4a73; /* Muted Dark Blue */ margin-bottom: 12px; cursor: pointer; transition: background-color 0.3s ease; - } - - .option input[type="radio"] { +} + +.option input[type="radio"] { margin-right: 12px; - accent-color: #4CAF50; - } - - .option:hover { - background-color: #f1f1f1; - } - - .submit-button { + accent-color: #f4e36b; /* Soft Yellow */ +} + +.option:hover { + background-color: #4c6a90; /* Lighter Muted Blue */ +} + +/* Submit Button */ +.submit-button { display: block; width: 100%; padding: 15px; - background-color: #007BFF; + background-color: #f57c00; /* Muted Orange */ color: white; border: none; border-radius: 8px; @@ -132,25 +135,25 @@ cursor: pointer; transition: background-color 0.3s ease; margin-top: 30px; - } - - .submit-button:hover { - background-color: #0056b3; - } - - /* Quiz completion styles */ - .quiz-completed { +} + +.submit-button:hover { + background-color: #f57c00; +} + +/* Quiz completion styles */ +.quiz-completed { text-align: center; font-size: 1.5rem; - color: #333; + color: #eceff1; /* Light Gray */ font-weight: 600; margin-top: 20px; - } - - .retry-button, - .back-button { +} + +.retry-button, +.back-button { padding: 14px 30px; - background-color: #FF5733; + background-color: #83c785; /* Green */ color: white; border: none; border-radius: 8px; @@ -158,36 +161,36 @@ font-size: 1.1rem; margin-top: 25px; transition: background-color 0.3s ease; - } - - .retry-button:hover, - .back-button:hover { - background-color: #e04e33; - } - - .result { +} + +.retry-button:hover, +.back-button:hover { + background-color: #83c785; +} + +/* Quiz Result */ +.result { font-size: 1.4rem; - color: #333; + color: #f4e36b; /* Soft Yellow */ margin-top: 20px; font-weight: 500; - } - - /* Responsive Design for Mobile */ - @media screen and (max-width: 768px) { +} + +/* Responsive Design for Mobile */ +@media screen and (max-width: 768px) { .quiz-container { - width: 90%; - padding: 20px; + width: 90%; + padding: 20px; } - + .subject-selection select { - width: 80%; + width: 80%; } - + .start-button, .submit-button, .retry-button, .back-button { - width: 100%; + width: 100%; } - } - \ No newline at end of file +} diff --git a/frontend/src/Quiz/Quiz.js b/frontend/src/Quiz/Quiz.js index 0762f81..50c9439 100644 --- a/frontend/src/Quiz/Quiz.js +++ b/frontend/src/Quiz/Quiz.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { useLocation } from "react-router-dom"; import "./Quiz.css"; @@ -15,31 +15,19 @@ const Quiz = () => { const location = useLocation(); - // Reset quiz when user navigates to the Quiz page - useEffect(() => { - setIsQuizStarted(false); - setIsQuizCompleted(false); - setShowSubjectSelection(true); - setQuizQuestions([]); // Ensure fresh state - setUserAnswers([]); - }, [location.pathname]); // This will reset on every navigation - - useEffect(() => { - if (isQuizStarted) { - fetchQuizQuestions(); - } - }, [subject, isQuizStarted]); - - const fetchQuizQuestions = async () => { + // Use useCallback to ensure that fetchQuizQuestions is stable + const fetchQuizQuestions = useCallback(async () => { setLoading(true); + setError(""); + try { const response = await fetch(`http://localhost:5000/quiz?subject=${subject}`); const data = await response.json(); - if (data.success) { + if (data.success && Array.isArray(data.quiz) && data.quiz.length > 0) { setQuizQuestions(data.quiz); setUserAnswers(new Array(data.quiz.length).fill("")); - setError(""); + setShowSubjectSelection(false); // Hide subject selection once quiz starts } else { setError("Failed to load quiz questions."); } @@ -49,7 +37,22 @@ const Quiz = () => { } finally { setLoading(false); } - }; + }, [subject]); // Dependency on 'subject' so it updates when subject changes + + useEffect(() => { + setIsQuizStarted(false); + setIsQuizCompleted(false); + setShowSubjectSelection(true); + setQuizQuestions([]); + setUserAnswers([]); + setError(""); + }, [location.pathname]); + + useEffect(() => { + if (isQuizStarted) { + fetchQuizQuestions(); + } + }, [isQuizStarted, fetchQuizQuestions]); // Add 'fetchQuizQuestions' as a dependency const handleAnswerChange = (index, answer) => { const updatedAnswers = [...userAnswers]; @@ -58,12 +61,14 @@ const Quiz = () => { }; const submitQuiz = async () => { - if (userAnswers.includes("")) { + if (userAnswers.includes("") || userAnswers.length !== quizQuestions.length) { setError("Please answer all questions before submitting."); return; } setLoading(true); + setError(""); + try { const response = await fetch("http://localhost:5000/quiz/submit", { method: "POST", @@ -74,9 +79,8 @@ const Quiz = () => { const data = await response.json(); if (data.success) { - setScore(data.score); // Correctly use score here + setScore(data.score); setIsQuizCompleted(true); - setShowSubjectSelection(false); } else { setError(data.message || "Error submitting quiz."); } @@ -88,38 +92,42 @@ const Quiz = () => { } }; - const startNewQuiz = () => { - // Reset quiz state to show fresh start - setIsQuizStarted(false); - setIsQuizCompleted(false); - setShowSubjectSelection(true); - setQuizQuestions([]); // Clear previous questions - setUserAnswers([]); // Clear previous answers + const startQuiz = () => { + setIsQuizStarted(true); + setShowSubjectSelection(false); }; - const goBackToSubjects = () => { - // Reset quiz state to show fresh start + const startNewQuiz = () => { setIsQuizStarted(false); setIsQuizCompleted(false); setShowSubjectSelection(true); - setQuizQuestions([]); // Clear previous questions - setUserAnswers([]); // Clear previous answers + setQuizQuestions([]); + setUserAnswers([]); + setError(""); }; return (

Test Your Knowledge

- {showSubjectSelection && ( + {showSubjectSelection && !isQuizStarted && (
- { + setSubject(e.target.value); // Ensure subject change triggers state updates + setIsQuizStarted(false); // Reset the quiz start state + setQuizQuestions([]); // Reset the quiz questions + setUserAnswers([]); // Reset the user's answers + }} + > -
@@ -164,9 +172,6 @@ const Quiz = () => { -
)} diff --git a/frontend/src/StudentCard/StudentCard.css b/frontend/src/StudentCard/StudentCard.css index e69de29..10ce2c2 100644 --- a/frontend/src/StudentCard/StudentCard.css +++ b/frontend/src/StudentCard/StudentCard.css @@ -0,0 +1,69 @@ +.student-card { + width: 100%; + max-width: 400px; + background: #1a3c5b; + border-radius: 12px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + padding: 20px; + margin: 20px auto; + text-align: center; + transition: transform 0.3s ease-in-out, box-shadow 0.3s ease-in-out; +} + +.student-card:hover { + transform: translateY(-5px); + box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); +} + +.student-card h2 { + font-size: 1.5rem; + color: #f4e36b; + margin-bottom: 10px; +} + +.student-card p { + font-size: 1rem; + color: #f4e36b; + margin: 5px 0; +} + +.student-progress { + width: 100%; + background: #f3f3f3; + border-radius: 6px; + overflow: hidden; + margin-top: 10px; +} + +.progress-bar { + height: 10px; + background: linear-gradient(90deg, #4CAF50, #2196F3); + border-radius: 6px; + transition: width 0.5s ease-in-out; +} + +.badge { + display: inline-block; + background: #ff9800; + color: white; + padding: 5px 10px; + font-size: 0.85rem; + border-radius: 15px; + margin-top: 10px; +} + +.student-card button { + background: #007bff; + color: white; + border: none; + padding: 10px 15px; + font-size: 1rem; + border-radius: 6px; + cursor: pointer; + margin-top: 10px; + transition: background 0.3s; +} + +.student-card button:hover { + background: #0056b3; +} diff --git a/frontend/src/StudentCard/StudentCard.js b/frontend/src/StudentCard/StudentCard.js index e69de29..cf29e4c 100644 --- a/frontend/src/StudentCard/StudentCard.js +++ b/frontend/src/StudentCard/StudentCard.js @@ -0,0 +1,98 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import "./StudentCard.css"; // Import styles + +const StudentCard = () => { + // Dummy student data (this would normally come from the backend) + const student = { + id: "123", + name: "Alice", + subject: "math", + progress: 80, + recentActivity: "Completed chapter on Algebra and History is still pending, Science is in progress" + }; + + const [subject, setSubject] = useState(student.subject || "math"); + const [progress, setProgress] = useState(student.progress || 0); + const [insights, setInsights] = useState(""); + const [interdisciplinarySuggestions, setInterdisciplinarySuggestions] = useState(""); + const [streaming, setStreaming] = useState(false); + + useEffect(() => { + if (student.recentActivity) { + //fetchInsights(subject, student.recentActivity); + } + }, [student]); + + const fetchInsights = async (subject, recentActivity) => { + try { + setStreaming(true); + const response = await axios.post("http://127.0.0.1:5000/api/student-insights", { + studentId: student.id, + subject, // The selected subject + recentActivity, // The recent activity + }); + + setInsights(response.data.aiFeedback || "No insights available"); + setInterdisciplinarySuggestions(response.data.interdisciplinarySuggestions || "No suggestions available"); + setStreaming(false); + } catch (error) { + console.error("Error fetching insights:", error); + setInsights("Error fetching insights."); + setStreaming(false); + } + }; + + return ( +
+

{student.name}

+ + {/* Subject Selection */} + + + + {/* Progress */} +
+
+
+

Progress: {progress}%

+ + {/* Insights and Interdisciplinary Suggestions */} +
+

AI Feedback:

+

{insights}

+

Interdisciplinary Suggestions:

+

{interdisciplinarySuggestions}

+
+ + {/* Refresh Insights Button */} + + + {/* Streaming Response */} + {streaming && ( +
+

Streaming response...

+
+ )} +
+ ); +}; + +export default StudentCard; diff --git a/frontend/src/TutorSession/TutorSession.css b/frontend/src/TutorSession/TutorSession.css index e869687..15c0aa9 100644 --- a/frontend/src/TutorSession/TutorSession.css +++ b/frontend/src/TutorSession/TutorSession.css @@ -2,7 +2,7 @@ max-width: 600px; margin: 40px auto; padding: 20px; - background: linear-gradient(135deg, #1e3c72, #2a5298); + background: linear-gradient(135deg, #1a3c5b, #1a3c5b); border-radius: 12px; box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2); text-align: center; @@ -13,6 +13,7 @@ font-size: 1.8rem; font-weight: bold; margin-bottom: 15px; + color: #f4e36b; } .label { @@ -33,7 +34,7 @@ } .question-input { - width: 100%; + width: 80%; height: 80px; padding: 10px; border-radius: 6px; @@ -84,5 +85,6 @@ .response-text { font-size: 1rem; word-wrap: break-word; + color:black } \ No newline at end of file