Harshal Vhatkar
commited on
Commit
·
9987aed
1
Parent(s):
28db8ae
add new features
Browse files- app.py +2 -2
- file_upload_vectorize.py +1 -1
- gen_mcqs.py +45 -4
- inclass_questions.py +738 -0
- session_page.py +845 -161
- session_page_alt.py +533 -120
app.py
CHANGED
|
@@ -3,8 +3,8 @@ import streamlit as st
|
|
| 3 |
from datetime import datetime, date, time, timedelta
|
| 4 |
from pathlib import Path
|
| 5 |
from utils.sample_data import SAMPLE_COURSES, SAMPLE_SESSIONS
|
| 6 |
-
from session_page_alt import display_session_content
|
| 7 |
-
|
| 8 |
from db import (
|
| 9 |
courses_collection2,
|
| 10 |
faculty_collection,
|
|
|
|
| 3 |
from datetime import datetime, date, time, timedelta
|
| 4 |
from pathlib import Path
|
| 5 |
from utils.sample_data import SAMPLE_COURSES, SAMPLE_SESSIONS
|
| 6 |
+
# from session_page_alt import display_session_content
|
| 7 |
+
from session_page import display_session_content
|
| 8 |
from db import (
|
| 9 |
courses_collection2,
|
| 10 |
faculty_collection,
|
file_upload_vectorize.py
CHANGED
|
@@ -25,7 +25,7 @@ resources_collection = db['resources']
|
|
| 25 |
# Configure APIs
|
| 26 |
openai.api_key = OPENAI_KEY
|
| 27 |
genai.configure(api_key=GEMINI_KEY)
|
| 28 |
-
model = genai.GenerativeModel('gemini-
|
| 29 |
|
| 30 |
def upload_resource(course_id, session_id, file_name, file_content, material_type):
|
| 31 |
# material_data = {
|
|
|
|
| 25 |
# Configure APIs
|
| 26 |
openai.api_key = OPENAI_KEY
|
| 27 |
genai.configure(api_key=GEMINI_KEY)
|
| 28 |
+
model = genai.GenerativeModel('gemini-1.5-flash')
|
| 29 |
|
| 30 |
def upload_resource(course_id, session_id, file_name, file_content, material_type):
|
| 31 |
# material_data = {
|
gen_mcqs.py
CHANGED
|
@@ -206,6 +206,7 @@
|
|
| 206 |
# return None
|
| 207 |
|
| 208 |
import ast
|
|
|
|
| 209 |
from pymongo import MongoClient
|
| 210 |
from datetime import datetime
|
| 211 |
import openai
|
|
@@ -305,6 +306,27 @@ def generate_mcqs(context, num_questions, session_title, session_description):
|
|
| 305 |
print(f"Error generating MCQs: , error: {e}")
|
| 306 |
return None
|
| 307 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
# New function to save quiz to database
|
| 309 |
def save_quiz(course_id, session_id, title, questions, user_id):
|
| 310 |
"""Save quiz to database"""
|
|
@@ -412,26 +434,45 @@ def submit_quiz_answers(quiz_id, student_id, student_answers):
|
|
| 412 |
if not quiz:
|
| 413 |
return None
|
| 414 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 415 |
# Calculate score
|
| 416 |
correct_answers = 0
|
| 417 |
-
total_questions = len(quiz['questions'])
|
| 418 |
|
| 419 |
for q_idx, question in enumerate(quiz['questions']):
|
| 420 |
student_answer = student_answers.get(str(q_idx))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
if student_answer: # Only check if answer was provided
|
| 422 |
# Extract the option letter (A, B, C, D) from the full answer string
|
| 423 |
-
answer_letter = student_answer.split(')')[0].strip()
|
| 424 |
-
if answer_letter == question['correct_option']:
|
|
|
|
|
|
|
| 425 |
correct_answers += 1
|
|
|
|
|
|
|
|
|
|
| 426 |
|
| 427 |
score = (correct_answers / total_questions) * 100
|
|
|
|
| 428 |
|
| 429 |
# Store submission
|
| 430 |
submission_data = {
|
| 431 |
"student_id": student_id,
|
| 432 |
"answers": student_answers,
|
| 433 |
"score": score,
|
| 434 |
-
"submitted_at": datetime.utcnow()
|
|
|
|
|
|
|
| 435 |
}
|
| 436 |
|
| 437 |
# Update quiz with submission
|
|
|
|
| 206 |
# return None
|
| 207 |
|
| 208 |
import ast
|
| 209 |
+
from typing import Dict, List
|
| 210 |
from pymongo import MongoClient
|
| 211 |
from datetime import datetime
|
| 212 |
import openai
|
|
|
|
| 306 |
print(f"Error generating MCQs: , error: {e}")
|
| 307 |
return None
|
| 308 |
|
| 309 |
+
def save_pre_class_quiz(course_id: str, session_id: str, title: str, questions: List[Dict], user_id: str, duration: int):
|
| 310 |
+
"""Save pre-class quiz to database"""
|
| 311 |
+
try:
|
| 312 |
+
quiz_data = {
|
| 313 |
+
"user_id": user_id,
|
| 314 |
+
"course_id": course_id,
|
| 315 |
+
"session_id": session_id,
|
| 316 |
+
"title": title,
|
| 317 |
+
"questions": questions,
|
| 318 |
+
"created_at": datetime.now(),
|
| 319 |
+
"status": "active",
|
| 320 |
+
"submissions": [],
|
| 321 |
+
"duration_minutes": duration,
|
| 322 |
+
"quiz_type": "pre_class"
|
| 323 |
+
}
|
| 324 |
+
result = quizzes_collection.insert_one(quiz_data)
|
| 325 |
+
return result.inserted_id
|
| 326 |
+
except Exception as e:
|
| 327 |
+
print(f"Error saving quiz: {e}")
|
| 328 |
+
return None
|
| 329 |
+
|
| 330 |
# New function to save quiz to database
|
| 331 |
def save_quiz(course_id, session_id, title, questions, user_id):
|
| 332 |
"""Save quiz to database"""
|
|
|
|
| 434 |
if not quiz:
|
| 435 |
return None
|
| 436 |
|
| 437 |
+
total_questions = len(quiz['questions'])
|
| 438 |
+
# Debug logging
|
| 439 |
+
print("\nScoring Debug:")
|
| 440 |
+
print(f"Total questions: {total_questions}")
|
| 441 |
+
|
| 442 |
# Calculate score
|
| 443 |
correct_answers = 0
|
|
|
|
| 444 |
|
| 445 |
for q_idx, question in enumerate(quiz['questions']):
|
| 446 |
student_answer = student_answers.get(str(q_idx))
|
| 447 |
+
correct_option = question['correct_option']
|
| 448 |
+
|
| 449 |
+
# Debug logging
|
| 450 |
+
print(f"\nQuestion {q_idx + 1}:")
|
| 451 |
+
print(f"Student answer: {student_answer}")
|
| 452 |
+
print(f"Correct option: {correct_option}")
|
| 453 |
+
|
| 454 |
if student_answer: # Only check if answer was provided
|
| 455 |
# Extract the option letter (A, B, C, D) from the full answer string
|
| 456 |
+
# answer_letter = student_answer.split(')')[0].strip()
|
| 457 |
+
# if answer_letter == question['correct_option']:
|
| 458 |
+
# correct_answers += 1
|
| 459 |
+
if student_answer == correct_option:
|
| 460 |
correct_answers += 1
|
| 461 |
+
print(f"✓ Correct! Total correct: {correct_answers}")
|
| 462 |
+
else:
|
| 463 |
+
print("✗ Incorrect")
|
| 464 |
|
| 465 |
score = (correct_answers / total_questions) * 100
|
| 466 |
+
print(f"\nFinal Score: {score}% ({correct_answers}/{total_questions} correct)")
|
| 467 |
|
| 468 |
# Store submission
|
| 469 |
submission_data = {
|
| 470 |
"student_id": student_id,
|
| 471 |
"answers": student_answers,
|
| 472 |
"score": score,
|
| 473 |
+
"submitted_at": datetime.utcnow(),
|
| 474 |
+
"correct_answers_count": correct_answers,
|
| 475 |
+
"total_questions": total_questions
|
| 476 |
}
|
| 477 |
|
| 478 |
# Update quiz with submission
|
inclass_questions.py
ADDED
|
@@ -0,0 +1,738 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import datetime, timedelta
|
| 2 |
+
from typing import Dict, List
|
| 3 |
+
from bson import ObjectId
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import streamlit as st
|
| 6 |
+
import json
|
| 7 |
+
import google.generativeai as genai
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
import os
|
| 10 |
+
from pymongo import MongoClient
|
| 11 |
+
import plotly.express as px
|
| 12 |
+
|
| 13 |
+
load_dotenv()
|
| 14 |
+
MONGO_URI = os.getenv('MONGO_URI')
|
| 15 |
+
GEMINI_API_KEY = os.getenv('GEMINI_KEY')
|
| 16 |
+
genai.configure(api_key=GEMINI_API_KEY)
|
| 17 |
+
model = genai.GenerativeModel("gemini-1.5-flash")
|
| 18 |
+
|
| 19 |
+
client = MongoClient(MONGO_URI)
|
| 20 |
+
db = client['novascholar_db']
|
| 21 |
+
courses_collection = db['courses']
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def generate_interval_questions(context: str, num_questions: int) -> List[Dict]:
|
| 25 |
+
"""Generate all interval questions at once"""
|
| 26 |
+
prompt = f"""
|
| 27 |
+
Generate {num_questions} quick check-in questions based on the following context.
|
| 28 |
+
Return ONLY a JSON array with this exact structure:
|
| 29 |
+
[
|
| 30 |
+
{{
|
| 31 |
+
"question_text": "Your question here",
|
| 32 |
+
"type": "mcq OR short_answer",
|
| 33 |
+
"options": ["option1", "option2", "option3", "option4"],
|
| 34 |
+
"correct_option": "correct answer",
|
| 35 |
+
"explanation": "brief explanation"
|
| 36 |
+
}}
|
| 37 |
+
]
|
| 38 |
+
|
| 39 |
+
Requirements:
|
| 40 |
+
- Each question should be answerable in 30-60 seconds
|
| 41 |
+
- Test understanding of different aspects of the material
|
| 42 |
+
- For MCQ, include exactly 4 options
|
| 43 |
+
- Questions should build in complexity
|
| 44 |
+
- **MAKE SURE THE NUMBER OF SHORT ANSWER QUESTIONS ARE MORE THAN MCQS**
|
| 45 |
+
- **MAKE SURE THE NUMBER OF MCQS DOES NOT EXCEED 2**
|
| 46 |
+
|
| 47 |
+
Context: {context}
|
| 48 |
+
"""
|
| 49 |
+
try:
|
| 50 |
+
response = model.generate_content(
|
| 51 |
+
prompt,
|
| 52 |
+
generation_config=genai.GenerationConfig(
|
| 53 |
+
temperature=0.3,
|
| 54 |
+
response_mime_type="application/json"
|
| 55 |
+
)
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
if not response.text:
|
| 59 |
+
raise ValueError("Empty response from model")
|
| 60 |
+
|
| 61 |
+
questions = json.loads(response.text)
|
| 62 |
+
return questions
|
| 63 |
+
|
| 64 |
+
except Exception as e:
|
| 65 |
+
st.error(f"Error generating questions: {e}")
|
| 66 |
+
return None
|
| 67 |
+
|
| 68 |
+
class IntervalQuestionManager:
|
| 69 |
+
def __init__(self, session_id: str, course_id: str):
|
| 70 |
+
self.session_id = session_id
|
| 71 |
+
self.course_id = course_id
|
| 72 |
+
self.questions = []
|
| 73 |
+
self.current_index = 0
|
| 74 |
+
self.interval_minutes = 10
|
| 75 |
+
# self.start_time = self._get_start_time()
|
| 76 |
+
self.start_time = None
|
| 77 |
+
# self.questions = self._load_existing_questions()
|
| 78 |
+
self._initialize_from_db()
|
| 79 |
+
|
| 80 |
+
# def _get_start_time(self):
|
| 81 |
+
# """Retrieve start time from database"""
|
| 82 |
+
# try:
|
| 83 |
+
# session_data = courses_collection.find_one(
|
| 84 |
+
# {
|
| 85 |
+
# "course_id": self.course_id,
|
| 86 |
+
# "sessions.session_id": self.session_id
|
| 87 |
+
# },
|
| 88 |
+
# {"sessions.$": 1}
|
| 89 |
+
# )
|
| 90 |
+
# if session_data and session_data.get('sessions'):
|
| 91 |
+
# session = session_data['sessions'][0]
|
| 92 |
+
# interval_data = session.get('in_class', {}).get('interval_questions', {})
|
| 93 |
+
# if interval_data and 'start_time' in interval_data:
|
| 94 |
+
# return interval_data['start_time']
|
| 95 |
+
# return None
|
| 96 |
+
# except Exception as e:
|
| 97 |
+
# st.error(f"Error getting start time: {e}")
|
| 98 |
+
# return None
|
| 99 |
+
|
| 100 |
+
# def _load_existing_questions(self) -> List[Dict]:
|
| 101 |
+
# """Load existing interval questions from database"""
|
| 102 |
+
# try:
|
| 103 |
+
# session_data = courses_collection.find_one(
|
| 104 |
+
# {
|
| 105 |
+
# "course_id": self.course_id,
|
| 106 |
+
# "sessions.session_id": self.session_id
|
| 107 |
+
# },
|
| 108 |
+
# {"sessions.$": 1}
|
| 109 |
+
# )
|
| 110 |
+
|
| 111 |
+
# if session_data and session_data.get('sessions'):
|
| 112 |
+
# session = session_data['sessions'][0]
|
| 113 |
+
# interval_data = session.get('in_class', {}).get('interval_questions', {})
|
| 114 |
+
# if interval_data and interval_data.get('questions'):
|
| 115 |
+
# print("Found existing questions:", len(interval_data['questions']))
|
| 116 |
+
# return interval_data['questions']
|
| 117 |
+
# return []
|
| 118 |
+
|
| 119 |
+
# except Exception as e:
|
| 120 |
+
# st.error(f"Error loading questions: {e}")
|
| 121 |
+
# return []
|
| 122 |
+
|
| 123 |
+
def _initialize_from_db(self):
|
| 124 |
+
"""Load existing questions and settings from database"""
|
| 125 |
+
try:
|
| 126 |
+
session_data = courses_collection.find_one(
|
| 127 |
+
{
|
| 128 |
+
"course_id": self.course_id,
|
| 129 |
+
"sessions.session_id": self.session_id
|
| 130 |
+
},
|
| 131 |
+
{"sessions.$": 1}
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
if session_data and session_data.get('sessions'):
|
| 135 |
+
session = session_data['sessions'][0]
|
| 136 |
+
interval_data = session.get('in_class', {}).get('interval_questions', {})
|
| 137 |
+
|
| 138 |
+
if interval_data:
|
| 139 |
+
self.questions = interval_data.get('questions', [])
|
| 140 |
+
self.interval_minutes = interval_data.get('interval_minutes', 10)
|
| 141 |
+
self.start_time = interval_data.get('start_time')
|
| 142 |
+
print(f"Loaded {len(self.questions)} questions, interval: {self.interval_minutes} mins")
|
| 143 |
+
|
| 144 |
+
except Exception as e:
|
| 145 |
+
st.error(f"Error initializing from database: {e}")
|
| 146 |
+
|
| 147 |
+
def initialize_questions(self, context: str, num_questions: int, interval_minutes: int):
|
| 148 |
+
if self.questions: # If questions already exist, don't regenerate
|
| 149 |
+
return True
|
| 150 |
+
|
| 151 |
+
"""Initialize all questions and save to database"""
|
| 152 |
+
questions = generate_interval_questions(context, num_questions)
|
| 153 |
+
if not questions:
|
| 154 |
+
return False
|
| 155 |
+
|
| 156 |
+
self.questions = []
|
| 157 |
+
self.interval_minutes = interval_minutes
|
| 158 |
+
|
| 159 |
+
for i, q in enumerate(questions):
|
| 160 |
+
question_doc = {
|
| 161 |
+
"_id": ObjectId(),
|
| 162 |
+
"question_text": q["question_text"],
|
| 163 |
+
"type": q["type"],
|
| 164 |
+
"options": q.get("options", []),
|
| 165 |
+
"correct_option": q.get("correct_option", ""),
|
| 166 |
+
"explanation": q.get("explanation", ""),
|
| 167 |
+
"display_after": i * interval_minutes, # When to display this question
|
| 168 |
+
"active": False,
|
| 169 |
+
"submissions": []
|
| 170 |
+
}
|
| 171 |
+
self.questions.append(question_doc)
|
| 172 |
+
|
| 173 |
+
# Save to database
|
| 174 |
+
result = courses_collection.update_one(
|
| 175 |
+
{
|
| 176 |
+
"course_id": self.course_id,
|
| 177 |
+
"sessions.session_id": self.session_id
|
| 178 |
+
},
|
| 179 |
+
{
|
| 180 |
+
"$set": {
|
| 181 |
+
"sessions.$.in_class.interval_questions": {
|
| 182 |
+
"questions": self.questions,
|
| 183 |
+
"interval_minutes": self.interval_minutes,
|
| 184 |
+
"start_time": None
|
| 185 |
+
}
|
| 186 |
+
}
|
| 187 |
+
}
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
return result.modified_count > 0
|
| 191 |
+
|
| 192 |
+
# def start_questions(self):
|
| 193 |
+
# """Start the interval questions"""
|
| 194 |
+
# try:
|
| 195 |
+
# self.start_time = datetime.now()
|
| 196 |
+
|
| 197 |
+
# # Update start time in database
|
| 198 |
+
# result = courses_collection.update_one(
|
| 199 |
+
# {
|
| 200 |
+
# "course_id": self.course_id,
|
| 201 |
+
# "sessions.session_id": self.session_id
|
| 202 |
+
# },
|
| 203 |
+
# {
|
| 204 |
+
# "$set": {
|
| 205 |
+
# "sessions.$.in_class.interval_questions.start_time": self.start_time
|
| 206 |
+
# }
|
| 207 |
+
# }
|
| 208 |
+
# )
|
| 209 |
+
|
| 210 |
+
# # Activate first question
|
| 211 |
+
# if self.questions:
|
| 212 |
+
# self.questions[0]["active"] = True
|
| 213 |
+
|
| 214 |
+
# # Update first question's active status in database
|
| 215 |
+
# courses_collection.update_one(
|
| 216 |
+
# {
|
| 217 |
+
# "course_id": self.course_id,
|
| 218 |
+
# "sessions.session_id": self.session_id,
|
| 219 |
+
# "sessions.in_class.questions._id": self.questions[0]["_id"]
|
| 220 |
+
# },
|
| 221 |
+
# {
|
| 222 |
+
# "$set": {
|
| 223 |
+
# "sessions.$.in_class.questions.$.active": True,
|
| 224 |
+
# "sessions.$.in_class.questions.$.started_at": self.start_time
|
| 225 |
+
# }
|
| 226 |
+
# }
|
| 227 |
+
# )
|
| 228 |
+
# if result.modified_count > 0:
|
| 229 |
+
# st.success("Questions started successfully!")
|
| 230 |
+
# else:
|
| 231 |
+
# st.error("Could not start questions. Please try again.")
|
| 232 |
+
# except Exception as e:
|
| 233 |
+
# st.error(f"Error starting questions: {str(e)}")
|
| 234 |
+
def start_questions(self):
|
| 235 |
+
"""Start the interval questions"""
|
| 236 |
+
try:
|
| 237 |
+
self.start_time = datetime.now()
|
| 238 |
+
|
| 239 |
+
result = courses_collection.update_one(
|
| 240 |
+
{
|
| 241 |
+
"course_id": self.course_id,
|
| 242 |
+
"sessions.session_id": self.session_id
|
| 243 |
+
},
|
| 244 |
+
{
|
| 245 |
+
"$set": {
|
| 246 |
+
"sessions.$.in_class.interval_questions.start_time": self.start_time
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
if result.modified_count > 0:
|
| 252 |
+
st.success("Questions started successfully!")
|
| 253 |
+
else:
|
| 254 |
+
st.error("Could not start questions. Please try again.")
|
| 255 |
+
|
| 256 |
+
except Exception as e:
|
| 257 |
+
st.error(f"Error starting questions: {str(e)}")
|
| 258 |
+
|
| 259 |
+
# def start_questions(self):
|
| 260 |
+
# """Start the interval questions"""
|
| 261 |
+
# self.start_time = datetime.now()
|
| 262 |
+
# courses_collection.update_one(
|
| 263 |
+
# {
|
| 264 |
+
# "course_id": self.course_id,
|
| 265 |
+
# "sessions.session_id": self.session_id
|
| 266 |
+
# },
|
| 267 |
+
# {
|
| 268 |
+
# "$set": {
|
| 269 |
+
# "sessions.$.in_class.interval_questions.start_time": self.start_time
|
| 270 |
+
# }
|
| 271 |
+
# }
|
| 272 |
+
# )
|
| 273 |
+
|
| 274 |
+
# def get_current_question(self) -> Dict:
|
| 275 |
+
# # """Get the currently active question based on elapsed time"""
|
| 276 |
+
# # if not self.start_time:
|
| 277 |
+
# # return None
|
| 278 |
+
|
| 279 |
+
# # elapsed_minutes = (datetime.now() - self.start_time).total_seconds() / 60
|
| 280 |
+
|
| 281 |
+
# # # Find the latest question that should be displayed
|
| 282 |
+
# # for question in reversed(self.questions):
|
| 283 |
+
# # if elapsed_minutes >= question["display_after"]:
|
| 284 |
+
# # return question
|
| 285 |
+
|
| 286 |
+
# # return None
|
| 287 |
+
|
| 288 |
+
# """Get current question based on elapsed time"""
|
| 289 |
+
# try:
|
| 290 |
+
# if not self.start_time:
|
| 291 |
+
# return None
|
| 292 |
+
|
| 293 |
+
# questions = self.questions
|
| 294 |
+
# if not questions:
|
| 295 |
+
# # Get questions from database
|
| 296 |
+
# session_data = courses_collection.find_one(
|
| 297 |
+
# {
|
| 298 |
+
# "course_id": self.course_id,
|
| 299 |
+
# "sessions.session_id": self.session_id
|
| 300 |
+
# },
|
| 301 |
+
# {"sessions.$": 1}
|
| 302 |
+
# )
|
| 303 |
+
|
| 304 |
+
# if not session_data or not session_data.get('sessions'):
|
| 305 |
+
# return None
|
| 306 |
+
|
| 307 |
+
# session = session_data['sessions'][0]
|
| 308 |
+
# interval_data = session.get('in_class', {}).get('interval_questions', {})
|
| 309 |
+
|
| 310 |
+
# if not interval_data:
|
| 311 |
+
# return None
|
| 312 |
+
|
| 313 |
+
# questions = interval_data.get('questions', [])
|
| 314 |
+
# interval_minutes = interval_data.get('interval_minutes', 10)
|
| 315 |
+
|
| 316 |
+
# # Calculate elapsed time
|
| 317 |
+
# elapsed_minutes = (datetime.now() - self.start_time).total_seconds() / 60
|
| 318 |
+
# question_index = int(elapsed_minutes // interval_minutes)
|
| 319 |
+
|
| 320 |
+
# # Debug logging
|
| 321 |
+
# print(f"Elapsed minutes: {elapsed_minutes}")
|
| 322 |
+
# print(f"Question index: {question_index}")
|
| 323 |
+
# print(f"Total questions: {len(questions)}")
|
| 324 |
+
|
| 325 |
+
# # Return current question if valid index
|
| 326 |
+
# if 0 <= question_index < len(questions):
|
| 327 |
+
# return questions[question_index]
|
| 328 |
+
# return None
|
| 329 |
+
|
| 330 |
+
# except Exception as e:
|
| 331 |
+
# st.error(f"Error getting current question: {e}")
|
| 332 |
+
# return None
|
| 333 |
+
def get_current_question(self) -> Dict:
|
| 334 |
+
"""Get current question based on elapsed time"""
|
| 335 |
+
try:
|
| 336 |
+
if not self.start_time or not self.questions:
|
| 337 |
+
return None
|
| 338 |
+
|
| 339 |
+
elapsed_minutes = (datetime.now() - self.start_time).total_seconds() / 60
|
| 340 |
+
question_index = int(elapsed_minutes // self.interval_minutes)
|
| 341 |
+
|
| 342 |
+
# Debug logging
|
| 343 |
+
print(f"Elapsed minutes: {elapsed_minutes}")
|
| 344 |
+
print(f"Question index: {question_index}")
|
| 345 |
+
print(f"Total questions: {len(self.questions)}")
|
| 346 |
+
print(f"Interval minutes: {self.interval_minutes}")
|
| 347 |
+
|
| 348 |
+
if 0 <= question_index < len(self.questions):
|
| 349 |
+
return self.questions[question_index]
|
| 350 |
+
return None
|
| 351 |
+
|
| 352 |
+
except Exception as e:
|
| 353 |
+
st.error(f"Error getting current question: {e}")
|
| 354 |
+
return None
|
| 355 |
+
|
| 356 |
+
def display_response_distribution(course_id: str, session_id: str, question_id: str):
|
| 357 |
+
"""Display real-time distribution of student responses"""
|
| 358 |
+
try:
|
| 359 |
+
session_data = courses_collection.find_one(
|
| 360 |
+
{
|
| 361 |
+
"course_id": course_id,
|
| 362 |
+
"sessions.session_id": session_id
|
| 363 |
+
},
|
| 364 |
+
{"sessions.$": 1}
|
| 365 |
+
)
|
| 366 |
+
|
| 367 |
+
if not session_data or not session_data.get('sessions'):
|
| 368 |
+
st.warning("No responses found.")
|
| 369 |
+
return
|
| 370 |
+
|
| 371 |
+
session = session_data['sessions'][0]
|
| 372 |
+
question = next(
|
| 373 |
+
(q for q in session['in_class']['interval_questions']['questions']
|
| 374 |
+
if str(q['_id']) == question_id),
|
| 375 |
+
None
|
| 376 |
+
)
|
| 377 |
+
|
| 378 |
+
if not question:
|
| 379 |
+
st.warning("Question not found.")
|
| 380 |
+
return
|
| 381 |
+
|
| 382 |
+
submissions = question.get('submissions', [])
|
| 383 |
+
|
| 384 |
+
if not submissions:
|
| 385 |
+
st.info("No responses received yet.")
|
| 386 |
+
return
|
| 387 |
+
|
| 388 |
+
# Calculate response distribution
|
| 389 |
+
if question['type'] == 'mcq':
|
| 390 |
+
responses = {}
|
| 391 |
+
for submission in submissions:
|
| 392 |
+
answer = submission['answer']
|
| 393 |
+
responses[answer] = responses.get(answer, 0) + 1
|
| 394 |
+
|
| 395 |
+
# Create DataFrame for visualization
|
| 396 |
+
df = pd.DataFrame(list(responses.items()), columns=['Option', 'Count'])
|
| 397 |
+
df['Percentage'] = df['Count'] / len(submissions) * 100
|
| 398 |
+
|
| 399 |
+
# Display metrics
|
| 400 |
+
total_responses = len(submissions)
|
| 401 |
+
st.metric("Total Responses", total_responses)
|
| 402 |
+
|
| 403 |
+
# Display bar chart
|
| 404 |
+
fig = px.bar(df,
|
| 405 |
+
x='Option',
|
| 406 |
+
y='Count',
|
| 407 |
+
title='Response Distribution',
|
| 408 |
+
text='Percentage')
|
| 409 |
+
fig.update_traces(texttemplate='%{text:.1f}%', textposition='outside')
|
| 410 |
+
st.plotly_chart(fig)
|
| 411 |
+
|
| 412 |
+
# Display detailed table
|
| 413 |
+
st.dataframe(df)
|
| 414 |
+
|
| 415 |
+
else: # Short answer responses
|
| 416 |
+
st.markdown("##### Responses Received")
|
| 417 |
+
# for submission in submissions:
|
| 418 |
+
# st.markdown(f"- {submission['answer']}")
|
| 419 |
+
|
| 420 |
+
st.metric("Total Responses", len(submissions))
|
| 421 |
+
|
| 422 |
+
except Exception as e:
|
| 423 |
+
st.error(f"Error displaying responses: {str(e)}")
|
| 424 |
+
|
| 425 |
+
def display_interval_question(course_id, session, question: Dict, user_type: str):
|
| 426 |
+
"""Display the interval question with different views for faculty/students"""
|
| 427 |
+
if not question:
|
| 428 |
+
return
|
| 429 |
+
|
| 430 |
+
st.markdown("#### Quick Questions ⚡")
|
| 431 |
+
|
| 432 |
+
try:
|
| 433 |
+
if user_type == "faculty":
|
| 434 |
+
with st.container():
|
| 435 |
+
st.markdown("##### Current Active Question")
|
| 436 |
+
st.markdown(f"**Question:** {question['question_text']}")
|
| 437 |
+
if question['type'] == 'mcq':
|
| 438 |
+
display_response_distribution(course_id, session['session_id'], str(question['_id']))
|
| 439 |
+
|
| 440 |
+
# Add controls to end question period
|
| 441 |
+
col1, col2 = st.columns([3,1])
|
| 442 |
+
with col2:
|
| 443 |
+
if st.button("End Question"):
|
| 444 |
+
end_question_period(course_id, session['session_id'], str(question['_id']))
|
| 445 |
+
st.rerun()
|
| 446 |
+
|
| 447 |
+
# Display all questions overview
|
| 448 |
+
st.markdown("##### All Questions Overview")
|
| 449 |
+
|
| 450 |
+
# Get all questions from the session
|
| 451 |
+
session_data = courses_collection.find_one(
|
| 452 |
+
{
|
| 453 |
+
"course_id": course_id,
|
| 454 |
+
"sessions.session_id": session['session_id']
|
| 455 |
+
},
|
| 456 |
+
{"sessions.$": 1}
|
| 457 |
+
)
|
| 458 |
+
|
| 459 |
+
if session_data and session_data.get('sessions'):
|
| 460 |
+
interval_questions = session_data['sessions'][0].get('in_class', {}).get('interval_questions', {})
|
| 461 |
+
all_questions = interval_questions.get('questions', [])
|
| 462 |
+
|
| 463 |
+
for idx, q in enumerate(all_questions, 1):
|
| 464 |
+
with st.expander(f"Question {idx}", expanded=False):
|
| 465 |
+
st.markdown(f"**Type:** {q['type'].upper()}")
|
| 466 |
+
st.markdown(f"**Question:** {q['question_text']}")
|
| 467 |
+
|
| 468 |
+
if q['type'] == 'mcq':
|
| 469 |
+
st.markdown("**Options:**")
|
| 470 |
+
for opt in q['options']:
|
| 471 |
+
if opt == q['correct_option']:
|
| 472 |
+
st.markdown(f"✅ {opt}")
|
| 473 |
+
else:
|
| 474 |
+
st.markdown(f"- {opt}")
|
| 475 |
+
|
| 476 |
+
st.markdown(f"**Explanation:** {q['explanation']}")
|
| 477 |
+
|
| 478 |
+
# Show when this question will be displayed
|
| 479 |
+
display_time = timedelta(minutes=q['display_after'])
|
| 480 |
+
st.info(f"Will be displayed after: {display_time}")
|
| 481 |
+
|
| 482 |
+
# Show submission count if any
|
| 483 |
+
submission_count = len(q.get('submissions', []))
|
| 484 |
+
if submission_count > 0:
|
| 485 |
+
st.metric("Responses received", submission_count)
|
| 486 |
+
|
| 487 |
+
else: # Student view
|
| 488 |
+
# Check if student has already submitted
|
| 489 |
+
# existing_submission = next(
|
| 490 |
+
# (sub for sub in question.get('submissions', [])
|
| 491 |
+
# if sub['student_id'] == st.session_state.user_id),
|
| 492 |
+
# None
|
| 493 |
+
# )
|
| 494 |
+
# Check for existing submission
|
| 495 |
+
existing_submission = _check_existing_submission(
|
| 496 |
+
course_id,
|
| 497 |
+
session['session_id'],
|
| 498 |
+
str(question['_id']),
|
| 499 |
+
st.session_state.user_id
|
| 500 |
+
)
|
| 501 |
+
if existing_submission:
|
| 502 |
+
st.success("Response submitted!")
|
| 503 |
+
st.markdown(f"Your answer: **{existing_submission['answer']}**")
|
| 504 |
+
|
| 505 |
+
else:
|
| 506 |
+
with st.form(key=f"question_form_{question['_id']}"):
|
| 507 |
+
st.markdown(f"**Question:** {question['question_text']}")
|
| 508 |
+
|
| 509 |
+
if question['type'] == 'mcq':
|
| 510 |
+
response = st.radio(
|
| 511 |
+
"Select your answer:",
|
| 512 |
+
options=question['options'],
|
| 513 |
+
key=f"mcq_{question['_id']}"
|
| 514 |
+
)
|
| 515 |
+
else:
|
| 516 |
+
response = st.text_area(
|
| 517 |
+
"Your answer:",
|
| 518 |
+
key=f"text_{question['_id']}"
|
| 519 |
+
)
|
| 520 |
+
|
| 521 |
+
if st.form_submit_button("Submit"):
|
| 522 |
+
if response:
|
| 523 |
+
submit_response(
|
| 524 |
+
course_id,
|
| 525 |
+
session['session_id'],
|
| 526 |
+
str(question['_id']),
|
| 527 |
+
st.session_state.user_id,
|
| 528 |
+
response
|
| 529 |
+
)
|
| 530 |
+
st.rerun()
|
| 531 |
+
else:
|
| 532 |
+
st.error("Please provide an answer before submitting.")
|
| 533 |
+
|
| 534 |
+
except Exception as e:
|
| 535 |
+
st.error(f"Error displaying question: {str(e)}")
|
| 536 |
+
|
| 537 |
+
def _check_existing_submission(course_id: str, session_id: str, question_id: str, student_id: str) -> Dict:
|
| 538 |
+
"""Check if student has already submitted an answer"""
|
| 539 |
+
try:
|
| 540 |
+
session_data = courses_collection.find_one(
|
| 541 |
+
{
|
| 542 |
+
"course_id": course_id,
|
| 543 |
+
"sessions.session_id": session_id,
|
| 544 |
+
"sessions.in_class.questions._id": ObjectId(question_id)
|
| 545 |
+
},
|
| 546 |
+
{"sessions.$": 1}
|
| 547 |
+
)
|
| 548 |
+
|
| 549 |
+
if session_data and session_data.get('sessions'):
|
| 550 |
+
session = session_data['sessions'][0]
|
| 551 |
+
question = next(
|
| 552 |
+
(q for q in session['in_class']['questions']
|
| 553 |
+
if str(q['_id']) == question_id),
|
| 554 |
+
None
|
| 555 |
+
)
|
| 556 |
+
|
| 557 |
+
if question:
|
| 558 |
+
return next(
|
| 559 |
+
(sub for sub in question.get('submissions', [])
|
| 560 |
+
if sub['student_id'] == student_id),
|
| 561 |
+
None
|
| 562 |
+
)
|
| 563 |
+
return None
|
| 564 |
+
except Exception as e:
|
| 565 |
+
st.error(f"Error checking submission: {e}")
|
| 566 |
+
return None
|
| 567 |
+
|
| 568 |
+
def end_question_period(course_id: str, session_id: str, question_id: str):
|
| 569 |
+
"""End the question period and mark the question as inactive"""
|
| 570 |
+
try:
|
| 571 |
+
result = courses_collection.update_one(
|
| 572 |
+
{
|
| 573 |
+
"course_id": course_id,
|
| 574 |
+
"sessions.session_id": session_id,
|
| 575 |
+
"sessions.in_class.questions._id": ObjectId(question_id)
|
| 576 |
+
},
|
| 577 |
+
{
|
| 578 |
+
"$set": {
|
| 579 |
+
"sessions.$.in_class.questions.$.active": False,
|
| 580 |
+
"sessions.$.in_class.questions.$.ended_at": datetime.utcnow()
|
| 581 |
+
}
|
| 582 |
+
}
|
| 583 |
+
)
|
| 584 |
+
|
| 585 |
+
if result.modified_count > 0:
|
| 586 |
+
st.success("Question period ended successfully!")
|
| 587 |
+
st.rerun()
|
| 588 |
+
else:
|
| 589 |
+
st.warning("Could not end question period. Question may not exist.")
|
| 590 |
+
|
| 591 |
+
except Exception as e:
|
| 592 |
+
st.error(f"Error ending question period: {e}")
|
| 593 |
+
|
| 594 |
+
|
| 595 |
+
# def submit_response(course_id: str, session_id: str, question_id: str, student_id: str, response: str):
|
| 596 |
+
# """Submit a student's response to a question"""
|
| 597 |
+
# try:
|
| 598 |
+
# # First find the specific session and question
|
| 599 |
+
# session_query = {
|
| 600 |
+
# "course_id": course_id,
|
| 601 |
+
# "sessions": {
|
| 602 |
+
# "$elemMatch": {
|
| 603 |
+
# "session_id": session_id,
|
| 604 |
+
# "in_class.interval_questions.questions._id": ObjectId(question_id)
|
| 605 |
+
# }
|
| 606 |
+
# }
|
| 607 |
+
# }
|
| 608 |
+
|
| 609 |
+
# # Check for existing submission
|
| 610 |
+
# session_doc = courses_collection.find_one(session_query)
|
| 611 |
+
# if not session_doc:
|
| 612 |
+
# st.error("Question not found")
|
| 613 |
+
# return
|
| 614 |
+
|
| 615 |
+
# session = next((s for s in session_doc['sessions'] if s['session_id'] == session_id), None)
|
| 616 |
+
# if not session:
|
| 617 |
+
# st.error("Session not found")
|
| 618 |
+
# return
|
| 619 |
+
|
| 620 |
+
# question = next((q for q in session['in_class']['questions']
|
| 621 |
+
# if str(q['_id']) == question_id), None)
|
| 622 |
+
# if not question:
|
| 623 |
+
# st.error("Question not found")
|
| 624 |
+
# return
|
| 625 |
+
|
| 626 |
+
# # Check for existing submission
|
| 627 |
+
# if any(sub['student_id'] == student_id for sub in question.get('submissions', [])):
|
| 628 |
+
# st.warning("You have already submitted an answer for this question.")
|
| 629 |
+
# return
|
| 630 |
+
|
| 631 |
+
# # Add new submission
|
| 632 |
+
# result = courses_collection.update_one(
|
| 633 |
+
# {
|
| 634 |
+
# "course_id": course_id,
|
| 635 |
+
# "sessions.session_id": session_id,
|
| 636 |
+
# "sessions.in_class.interval_questions.questions._id": ObjectId(question_id)
|
| 637 |
+
# },
|
| 638 |
+
# {
|
| 639 |
+
# "$push": {
|
| 640 |
+
# "sessions.$[session].in_class.interval_questions.questions.$[question].submissions": {
|
| 641 |
+
# "student_id": student_id,
|
| 642 |
+
# "answer": response,
|
| 643 |
+
# "submitted_at": datetime.utcnow()
|
| 644 |
+
# }
|
| 645 |
+
# }
|
| 646 |
+
# },
|
| 647 |
+
# array_filters=[
|
| 648 |
+
# {"session.session_id": session_id},
|
| 649 |
+
# {"question._id": ObjectId(question_id)}
|
| 650 |
+
# ]
|
| 651 |
+
# )
|
| 652 |
+
# if result.modified_count > 0:
|
| 653 |
+
# st.success("Response submitted successfully!")
|
| 654 |
+
# st.rerun()
|
| 655 |
+
# else:
|
| 656 |
+
# st.error("Could not submit response. Question may not exist or be inactive.")
|
| 657 |
+
|
| 658 |
+
# except Exception as e:
|
| 659 |
+
# st.error(f"Error submitting response: {str(e)}")
|
| 660 |
+
def submit_response(course_id: str, session_id: str, question_id: str, student_id: str, response: str):
|
| 661 |
+
"""Submit a student's response to a question"""
|
| 662 |
+
try:
|
| 663 |
+
# Debug prints
|
| 664 |
+
print(f"Submitting response for question {question_id}")
|
| 665 |
+
print(f"Course: {course_id}, Session: {session_id}")
|
| 666 |
+
|
| 667 |
+
# First find the specific session and question with correct path
|
| 668 |
+
session_doc = courses_collection.find_one(
|
| 669 |
+
{
|
| 670 |
+
"course_id": course_id,
|
| 671 |
+
"sessions.session_id": session_id
|
| 672 |
+
},
|
| 673 |
+
{"sessions.$": 1}
|
| 674 |
+
)
|
| 675 |
+
|
| 676 |
+
if not session_doc:
|
| 677 |
+
print("Session document not found")
|
| 678 |
+
st.error("Session not found")
|
| 679 |
+
return
|
| 680 |
+
|
| 681 |
+
session = session_doc['sessions'][0]
|
| 682 |
+
|
| 683 |
+
# Get interval questions
|
| 684 |
+
interval_questions = session.get('in_class', {}).get('interval_questions', {})
|
| 685 |
+
if not interval_questions:
|
| 686 |
+
print("No interval questions found")
|
| 687 |
+
st.error("No questions found")
|
| 688 |
+
return
|
| 689 |
+
|
| 690 |
+
questions = interval_questions.get('questions', [])
|
| 691 |
+
|
| 692 |
+
# Find specific question
|
| 693 |
+
question = next(
|
| 694 |
+
(q for q in questions if str(q['_id']) == question_id),
|
| 695 |
+
None
|
| 696 |
+
)
|
| 697 |
+
if not question:
|
| 698 |
+
print(f"Question {question_id} not found in questions list")
|
| 699 |
+
st.error("Question not found")
|
| 700 |
+
return
|
| 701 |
+
|
| 702 |
+
# Check for existing submission
|
| 703 |
+
if any(sub['student_id'] == student_id for sub in question.get('submissions', [])):
|
| 704 |
+
st.warning("You have already submitted an answer for this question.")
|
| 705 |
+
return
|
| 706 |
+
|
| 707 |
+
# Add new submission with correct path
|
| 708 |
+
result = courses_collection.update_one(
|
| 709 |
+
{
|
| 710 |
+
"course_id": course_id,
|
| 711 |
+
"sessions.session_id": session_id,
|
| 712 |
+
"sessions.in_class.interval_questions.questions._id": ObjectId(question_id)
|
| 713 |
+
},
|
| 714 |
+
{
|
| 715 |
+
"$push": {
|
| 716 |
+
"sessions.$[ses].in_class.interval_questions.questions.$[q].submissions": {
|
| 717 |
+
"student_id": student_id,
|
| 718 |
+
"answer": response,
|
| 719 |
+
"submitted_at": datetime.utcnow()
|
| 720 |
+
}
|
| 721 |
+
}
|
| 722 |
+
},
|
| 723 |
+
array_filters=[
|
| 724 |
+
{"ses.session_id": session_id},
|
| 725 |
+
{"q._id": ObjectId(question_id)}
|
| 726 |
+
]
|
| 727 |
+
)
|
| 728 |
+
print(f"Update result: {result.modified_count}")
|
| 729 |
+
|
| 730 |
+
if result.modified_count > 0:
|
| 731 |
+
st.success("Response submitted successfully!")
|
| 732 |
+
st.rerun()
|
| 733 |
+
else:
|
| 734 |
+
st.error("Could not submit response. Please try again.")
|
| 735 |
+
|
| 736 |
+
except Exception as e:
|
| 737 |
+
print(f"Error details: {str(e)}")
|
| 738 |
+
st.error(f"Error submitting response: {str(e)}")
|
session_page.py
CHANGED
|
@@ -5,6 +5,7 @@ import requests
|
|
| 5 |
import streamlit as st
|
| 6 |
from datetime import datetime, timedelta
|
| 7 |
from youtube_transcript_api import YouTubeTranscriptApi
|
|
|
|
| 8 |
from utils.helpers import display_progress_bar, create_notification, format_datetime
|
| 9 |
from file_upload_vectorize import upload_resource, extract_text_from_file, create_vector_store, resources_collection, model, assignment_submit
|
| 10 |
from db import courses_collection2, chat_history_collection, students_collection, faculty_collection, vectors_collection
|
|
@@ -16,7 +17,7 @@ import plotly.express as px
|
|
| 16 |
from dotenv import load_dotenv
|
| 17 |
import os
|
| 18 |
from pymongo import MongoClient
|
| 19 |
-
from gen_mcqs import generate_mcqs, save_quiz, quizzes_collection, get_student_quiz_score, submit_quiz_answers
|
| 20 |
from create_course import courses_collection
|
| 21 |
# from pre_class_analytics import NovaScholarAnalytics
|
| 22 |
from pre_class_analytics2 import NovaScholarAnalytics
|
|
@@ -53,6 +54,7 @@ assignment_evaluation_collection = db["assignment_evaluation"]
|
|
| 53 |
subjective_tests_collection = db["subjective_tests"]
|
| 54 |
synoptic_store_collection = db["synoptic_store"]
|
| 55 |
assignments_collection = db["assignments"]
|
|
|
|
| 56 |
|
| 57 |
# for implementing Context Caching:
|
| 58 |
# PROJECT_ID = "novascholar-446709"
|
|
@@ -265,129 +267,263 @@ def display_preclass_content(session, student_id, course_id):
|
|
| 265 |
|
| 266 |
# Chat input
|
| 267 |
# Add a check, if materials are available, only then show the chat input
|
| 268 |
-
if(st.session_state.user_type == "student"):
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
|
| 309 |
-
|
| 310 |
-
|
| 311 |
|
| 312 |
-
|
| 313 |
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
# Update database
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
try:
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 391 |
|
| 392 |
else:
|
| 393 |
st.subheader("Upload Pre-class Material")
|
|
@@ -432,6 +568,9 @@ def display_preclass_content(session, student_id, course_id):
|
|
| 432 |
st.session_state.messages = []
|
| 433 |
|
| 434 |
if st.session_state.user_type == 'student':
|
|
|
|
|
|
|
|
|
|
| 435 |
st.subheader("Create a Practice Quiz")
|
| 436 |
questions = []
|
| 437 |
quiz_id = ""
|
|
@@ -574,6 +713,93 @@ def display_preclass_content(session, student_id, course_id):
|
|
| 574 |
# except Exception as db_error:
|
| 575 |
# st.error(f"Error saving submission: {str(db_error)}")
|
| 576 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 577 |
import requests
|
| 578 |
|
| 579 |
def get_supported_url_formats():
|
|
@@ -771,12 +997,12 @@ def upload_video_source(course_id, session_id, video_url):
|
|
| 771 |
""")
|
| 772 |
return None
|
| 773 |
|
| 774 |
-
def upload_preclass_materials(session_id, course_id):
|
| 775 |
"""Upload pre-class materials and manage external resources for a session"""
|
| 776 |
st.subheader("Pre-class Materials Management")
|
| 777 |
|
| 778 |
# Create tabs for different functionalities
|
| 779 |
-
upload_tab, videos_tab, web_resources ,external_tab= st.tabs(["Upload Materials","Upload Video Sources","Web Resources", "Resources by Perplexity"])
|
| 780 |
|
| 781 |
with upload_tab:
|
| 782 |
# Original file upload functionality
|
|
@@ -790,6 +1016,23 @@ def upload_preclass_materials(session_id, course_id):
|
|
| 790 |
if st.button("Upload Material"):
|
| 791 |
upload_resource(course_id, session_id, file_name, uploaded_file, material_type)
|
| 792 |
st.success("Material uploaded successfully!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 793 |
with videos_tab:
|
| 794 |
# Upload video sources
|
| 795 |
st.info("Upload video sources for this session.")
|
|
@@ -805,7 +1048,7 @@ def upload_preclass_materials(session_id, course_id):
|
|
| 805 |
st.info("""
|
| 806 |
Share online resources with your students. Supported links include:
|
| 807 |
- Google Colab notebooks
|
| 808 |
-
- Google Slides
|
| 809 |
- Online documentation
|
| 810 |
- Educational websites
|
| 811 |
- Any web-based learning resource
|
|
@@ -926,21 +1169,196 @@ def upload_preclass_materials(session_id, course_id):
|
|
| 926 |
- URL: [{resource['url']}]({resource['url']})
|
| 927 |
""")
|
| 928 |
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 935 |
|
| 936 |
-
|
| 937 |
-
|
| 938 |
-
|
| 939 |
-
|
| 940 |
-
|
| 941 |
-
|
| 942 |
-
|
| 943 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 944 |
|
| 945 |
def extract_external_content(url, content_type):
|
| 946 |
"""Extract content from external resources based on their type"""
|
|
@@ -1445,26 +1863,26 @@ def display_session_outline(session, outline, course_id):
|
|
| 1445 |
# st.markdown('</div>', unsafe_allow_html=True)
|
| 1446 |
|
| 1447 |
# Add save/download options
|
| 1448 |
-
if st.button("💾 Save Outline"):
|
| 1449 |
-
|
| 1450 |
-
|
| 1451 |
-
|
| 1452 |
-
|
| 1453 |
-
|
| 1454 |
-
|
| 1455 |
-
|
| 1456 |
-
|
| 1457 |
-
|
| 1458 |
-
# Download option
|
| 1459 |
-
if st.download_button(
|
| 1460 |
-
|
| 1461 |
-
|
| 1462 |
-
|
| 1463 |
-
|
| 1464 |
-
):
|
| 1465 |
-
|
| 1466 |
-
|
| 1467 |
-
|
| 1468 |
def display_in_class_content(session, user_type, course_id, user_id):
|
| 1469 |
# """Display in-class activities and interactions"""
|
| 1470 |
"""Display in-class activities and interactions"""
|
|
@@ -1526,16 +1944,116 @@ def display_in_class_content(session, user_type, course_id, user_id):
|
|
| 1526 |
outline = generate_session_outline(session['session_id'], course_id)
|
| 1527 |
if outline:
|
| 1528 |
display_session_outline(session, outline, course_id)
|
| 1529 |
-
st.rerun()
|
| 1530 |
print("Session outline is: ", outline)
|
| 1531 |
# Save outline to database
|
| 1532 |
-
|
| 1533 |
-
|
| 1534 |
-
|
| 1535 |
-
|
|
|
|
| 1536 |
else:
|
| 1537 |
display_session_outline(session, st.session_state.session_outline, course_id)
|
| 1538 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1539 |
# Initialize Live Polls feature
|
| 1540 |
live_polls = LivePollFeature()
|
| 1541 |
|
|
@@ -2676,6 +3194,56 @@ def display_preclass_analytics2(session, course_id):
|
|
| 2676 |
# st.markdown('</div>', unsafe_allow_html=True)
|
| 2677 |
|
| 2678 |
# Display student metrics in a grid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2679 |
st.markdown('<div class="analytics-grid">', unsafe_allow_html=True)
|
| 2680 |
for student in analytics["student_analytics"]:
|
| 2681 |
# Apply filters
|
|
@@ -2695,10 +3263,12 @@ def display_preclass_analytics2(session, course_id):
|
|
| 2695 |
if struggling_topic != "All" and struggling_topic not in student["struggling_topics"]:
|
| 2696 |
continue
|
| 2697 |
|
|
|
|
|
|
|
| 2698 |
st.markdown(f"""
|
| 2699 |
<div class="student-metrics-card">
|
| 2700 |
<div class="header">
|
| 2701 |
-
<span class="student-id">Student {
|
| 2702 |
</div>
|
| 2703 |
<div class="metrics-grid">
|
| 2704 |
<div class="metric-box">
|
|
@@ -2901,7 +3471,7 @@ def display_session_content(student_id, course_id, session, username, user_type)
|
|
| 2901 |
"Evaluate Subjective Tests"
|
| 2902 |
])
|
| 2903 |
with tabs[0]:
|
| 2904 |
-
upload_preclass_materials(session['session_id'], course_id)
|
| 2905 |
with tabs[1]:
|
| 2906 |
display_in_class_content(session, user_type, course_id, user_type)
|
| 2907 |
with tabs[2]:
|
|
@@ -3278,6 +3848,120 @@ def display_subjective_test_tab(student_id, course_id, session_id):
|
|
| 3278 |
st.error("An error occurred while loading the tests. Please try again later.")
|
| 3279 |
print(f"Error in display_subjective_test_tab: {str(e)}")
|
| 3280 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3281 |
def display_test_results(test_id, student_id):
|
| 3282 |
"""
|
| 3283 |
Display test results and analysis for a student
|
|
|
|
| 5 |
import streamlit as st
|
| 6 |
from datetime import datetime, timedelta
|
| 7 |
from youtube_transcript_api import YouTubeTranscriptApi
|
| 8 |
+
from session_page_alt import display_pre_test_results, pre_generate_questions, pre_save_subjective_test, submit_pre_subjective_test
|
| 9 |
from utils.helpers import display_progress_bar, create_notification, format_datetime
|
| 10 |
from file_upload_vectorize import upload_resource, extract_text_from_file, create_vector_store, resources_collection, model, assignment_submit
|
| 11 |
from db import courses_collection2, chat_history_collection, students_collection, faculty_collection, vectors_collection
|
|
|
|
| 17 |
from dotenv import load_dotenv
|
| 18 |
import os
|
| 19 |
from pymongo import MongoClient
|
| 20 |
+
from gen_mcqs import generate_mcqs, save_pre_class_quiz, save_quiz, quizzes_collection, get_student_quiz_score, submit_quiz_answers
|
| 21 |
from create_course import courses_collection
|
| 22 |
# from pre_class_analytics import NovaScholarAnalytics
|
| 23 |
from pre_class_analytics2 import NovaScholarAnalytics
|
|
|
|
| 54 |
subjective_tests_collection = db["subjective_tests"]
|
| 55 |
synoptic_store_collection = db["synoptic_store"]
|
| 56 |
assignments_collection = db["assignments"]
|
| 57 |
+
chat_time_collection = db["chat_time"]
|
| 58 |
|
| 59 |
# for implementing Context Caching:
|
| 60 |
# PROJECT_ID = "novascholar-446709"
|
|
|
|
| 267 |
|
| 268 |
# Chat input
|
| 269 |
# Add a check, if materials are available, only then show the chat input
|
| 270 |
+
# if(st.session_state.user_type == "student"):
|
| 271 |
+
# if materials:
|
| 272 |
+
# if prompt := st.chat_input("Ask a question about Pre-class Materials"):
|
| 273 |
+
# # if len(st.session_state.messages) >= 20:
|
| 274 |
+
# # st.warning("Message limit (20) reached for this session.")
|
| 275 |
+
# # return
|
| 276 |
+
|
| 277 |
+
# st.session_state.messages.append({"role": "user", "content": prompt})
|
| 278 |
+
|
| 279 |
+
# # Display User Message
|
| 280 |
+
# with st.chat_message("user"):
|
| 281 |
+
# st.markdown(prompt)
|
| 282 |
+
|
| 283 |
+
# # Get document context
|
| 284 |
+
# context = ""
|
| 285 |
+
# print("Session ID is: ", session['session_id'])
|
| 286 |
+
# materials = resources_collection.find({"session_id": session['session_id']})
|
| 287 |
+
# print(materials)
|
| 288 |
+
# context = ""
|
| 289 |
+
# vector_data = None
|
| 290 |
+
|
| 291 |
+
# # for material in materials:
|
| 292 |
+
# # print(material)
|
| 293 |
+
# context = ""
|
| 294 |
+
# for material in materials:
|
| 295 |
+
# resource_id = material['_id']
|
| 296 |
+
# print("Supposed Resource ID is: ", resource_id)
|
| 297 |
+
# vector_data = vectors_collection.find_one({"resource_id": resource_id})
|
| 298 |
+
# # print(vector_data)
|
| 299 |
+
# if vector_data and 'text' in vector_data:
|
| 300 |
+
# context += vector_data['text'] + "\n"
|
| 301 |
+
|
| 302 |
+
# if not vector_data:
|
| 303 |
+
# st.error("No Pre-class materials found for this session.")
|
| 304 |
+
# return
|
| 305 |
+
|
| 306 |
+
# try:
|
| 307 |
+
# # Generate response using Gemini
|
| 308 |
+
# # context_prompt = f"""
|
| 309 |
+
# # Based on the following context, answer the user's question:
|
| 310 |
|
| 311 |
+
# # Context:
|
| 312 |
+
# # {context}
|
| 313 |
|
| 314 |
+
# # Question: {prompt}
|
| 315 |
|
| 316 |
+
# # Please provide a clear and concise answer based only on the information provided in the context.
|
| 317 |
+
# # """
|
| 318 |
+
# # context_prompt = f"""
|
| 319 |
+
# # You are a highly intelligent and resourceful assistant capable of synthesizing information from the provided context.
|
| 320 |
+
|
| 321 |
+
# # Context:
|
| 322 |
+
# # {context}
|
| 323 |
+
|
| 324 |
+
# # Instructions:
|
| 325 |
+
# # 1. Base your answers primarily on the given context.
|
| 326 |
+
# # 2. If the answer to the user's question is not explicitly in the context but can be inferred or synthesized from the information provided, do so thoughtfully.
|
| 327 |
+
# # 3. Only use external knowledge or web assistance when:
|
| 328 |
+
# # - The context lacks sufficient information, and
|
| 329 |
+
# # - The question requires knowledge beyond what can be reasonably inferred from the context.
|
| 330 |
+
# # 4. Clearly state if you are relying on web assistance for any part of your answer.
|
| 331 |
+
# # 5. Do not respond with a negative. If the answer is not in the context, provide a thoughtful response based on the information available on the web about it.
|
| 332 |
+
|
| 333 |
+
# # Question: {prompt}
|
| 334 |
+
|
| 335 |
+
# # Please provide a clear and comprehensive answer based on the above instructions.
|
| 336 |
+
# # """
|
| 337 |
+
# context_prompt = f"""
|
| 338 |
+
# You are a highly intelligent and resourceful assistant capable of synthesizing information from the provided context and external sources.
|
| 339 |
+
|
| 340 |
+
# Context:
|
| 341 |
+
# {context}
|
| 342 |
+
|
| 343 |
+
# Instructions:
|
| 344 |
+
# 1. Base your answers on the provided context wherever possible.
|
| 345 |
+
# 2. If the answer to the user's question is not explicitly in the context:
|
| 346 |
+
# - Use external knowledge or web assistance to provide a clear and accurate response.
|
| 347 |
+
# 3. Do not respond negatively. If the answer is not in the context, use web assistance or your knowledge to generate a thoughtful response.
|
| 348 |
+
# 4. Clearly state if part of your response relies on web assistance.
|
| 349 |
+
|
| 350 |
+
# Question: {prompt}
|
| 351 |
+
|
| 352 |
+
# Please provide a clear and comprehensive answer based on the above instructions.
|
| 353 |
+
# """
|
| 354 |
+
|
| 355 |
+
# response = model.generate_content(context_prompt)
|
| 356 |
+
# if not response or not response.text:
|
| 357 |
+
# st.error("No response received from the model")
|
| 358 |
+
# return
|
| 359 |
+
|
| 360 |
+
# assistant_response = response.text
|
| 361 |
+
# # Display Assistant Response
|
| 362 |
+
# with st.chat_message("assistant"):
|
| 363 |
+
# st.markdown(assistant_response)
|
| 364 |
+
|
| 365 |
+
# # Build the message
|
| 366 |
+
# new_message = {
|
| 367 |
+
# "prompt": prompt,
|
| 368 |
+
# "response": assistant_response,
|
| 369 |
+
# "timestamp": datetime.utcnow()
|
| 370 |
+
# }
|
| 371 |
+
# st.session_state.messages.append(new_message)
|
| 372 |
+
# # Update database
|
| 373 |
+
# try:
|
| 374 |
+
# chat_history_collection.update_one(
|
| 375 |
+
# {
|
| 376 |
+
# "user_id": student_id,
|
| 377 |
+
# "session_id": session['session_id']
|
| 378 |
+
# },
|
| 379 |
+
# {
|
| 380 |
+
# "$push": {"messages": new_message},
|
| 381 |
+
# "$setOnInsert": {
|
| 382 |
+
# "user_id": student_id,
|
| 383 |
+
# "session_id": session['session_id'],
|
| 384 |
+
# "timestamp": datetime.utcnow()
|
| 385 |
+
# }
|
| 386 |
+
# },
|
| 387 |
+
# upsert=True
|
| 388 |
+
# )
|
| 389 |
+
# except Exception as db_error:
|
| 390 |
+
# st.error(f"Error saving chat history: {str(db_error)}")
|
| 391 |
+
# except Exception as e:
|
| 392 |
+
# st.error(f"Error generating response: {str(e)}")
|
| 393 |
+
|
| 394 |
+
if 'chat_session_active' not in st.session_state:
|
| 395 |
+
st.session_state.chat_session_active = False
|
| 396 |
+
if 'chat_session_start' not in st.session_state:
|
| 397 |
+
st.session_state.chat_session_start = None
|
| 398 |
|
| 399 |
+
# Chat input
|
| 400 |
+
# Add a check, if materials are available, only then show the chat input
|
| 401 |
+
if st.session_state.user_type == "student":
|
| 402 |
+
if materials:
|
| 403 |
+
# Start chat session button
|
| 404 |
+
chat_time = chat_time_collection.find_one(
|
| 405 |
+
{"session_id": session["session_id"], "user_id": student_id, "course_id": course_id}
|
| 406 |
+
)
|
| 407 |
|
| 408 |
+
# Calculate session status and time remaining
|
| 409 |
+
current_time = datetime.now()
|
| 410 |
+
chat_active = False
|
| 411 |
+
time_remaining = None
|
| 412 |
+
|
| 413 |
+
if chat_time and "start_time" in chat_time:
|
| 414 |
+
session_end_time = chat_time["start_time"] + timedelta(minutes=20)
|
| 415 |
+
time_remaining = session_end_time - current_time
|
| 416 |
+
chat_active = time_remaining.total_seconds() > 0
|
| 417 |
+
|
| 418 |
+
# Display appropriate interface based on session status
|
| 419 |
+
if not chat_time or "start_time" not in chat_time:
|
| 420 |
+
# New session case
|
| 421 |
+
if st.button("Start Chat Session (20 minutes)"):
|
| 422 |
+
st.session_state.chat_session_start = current_time
|
| 423 |
+
st.session_state.chat_session_active = True
|
| 424 |
+
|
| 425 |
# Update database
|
| 426 |
+
chat_time_collection.update_one(
|
| 427 |
+
{
|
| 428 |
+
"session_id": session["session_id"],
|
| 429 |
+
"user_id": student_id,
|
| 430 |
+
"course_id": course_id
|
| 431 |
+
},
|
| 432 |
+
{
|
| 433 |
+
"$set": {
|
| 434 |
+
"start_time": current_time,
|
| 435 |
+
"end_time": current_time + timedelta(minutes=20)
|
| 436 |
+
}
|
| 437 |
+
},
|
| 438 |
+
upsert=True
|
| 439 |
+
)
|
| 440 |
+
st.rerun()
|
| 441 |
+
|
| 442 |
+
elif chat_active:
|
| 443 |
+
# Active session case
|
| 444 |
+
minutes = int(time_remaining.total_seconds() // 60)
|
| 445 |
+
seconds = int(time_remaining.total_seconds() % 60)
|
| 446 |
+
st.info(f"⏱️ Time remaining: {minutes:02d}:{seconds:02d}")
|
| 447 |
+
|
| 448 |
+
# Show chat input
|
| 449 |
+
if prompt := st.chat_input("Ask a question about Pre-class Materials"):
|
| 450 |
+
st.session_state.messages.append({"role": "user", "content": prompt})
|
| 451 |
+
|
| 452 |
+
# Display User Message
|
| 453 |
+
with st.chat_message("user"):
|
| 454 |
+
st.markdown(prompt)
|
| 455 |
+
|
| 456 |
try:
|
| 457 |
+
context = get_chat_context(session['session_id'])
|
| 458 |
+
try:
|
| 459 |
+
context_prompt = f"""
|
| 460 |
+
You are a highly intelligent and resourceful assistant capable of synthesizing information from the provided context and external sources.
|
| 461 |
+
|
| 462 |
+
Context:
|
| 463 |
+
{context}
|
| 464 |
+
|
| 465 |
+
Instructions:
|
| 466 |
+
1. Base your answers on the provided context wherever possible.
|
| 467 |
+
2. If the answer to the user's question is not explicitly in the context:
|
| 468 |
+
- Use external knowledge or web assistance to provide a clear and accurate response.
|
| 469 |
+
3. Do not respond negatively. If the answer is not in the context, use web assistance or your knowledge to generate a thoughtful response.
|
| 470 |
+
4. Clearly state if part of your response relies on web assistance.
|
| 471 |
+
|
| 472 |
+
Question: {prompt}
|
| 473 |
+
|
| 474 |
+
Please provide a clear and comprehensive answer based on the above instructions.
|
| 475 |
+
"""
|
| 476 |
+
|
| 477 |
+
response = model.generate_content(context_prompt)
|
| 478 |
+
if not response or not response.text:
|
| 479 |
+
st.error("No response received from the model")
|
| 480 |
+
return
|
| 481 |
+
|
| 482 |
+
assistant_response = response.text
|
| 483 |
+
# Display Assistant Response
|
| 484 |
+
with st.chat_message("assistant"):
|
| 485 |
+
st.markdown(assistant_response)
|
| 486 |
+
|
| 487 |
+
# Build the message
|
| 488 |
+
new_message = {
|
| 489 |
+
"prompt": prompt,
|
| 490 |
+
"response": assistant_response,
|
| 491 |
+
"timestamp": datetime.utcnow(),
|
| 492 |
+
}
|
| 493 |
+
st.session_state.messages.append(new_message)
|
| 494 |
+
|
| 495 |
+
# Update database
|
| 496 |
+
try:
|
| 497 |
+
chat_history_collection.update_one(
|
| 498 |
+
{
|
| 499 |
+
"user_id": student_id,
|
| 500 |
+
"session_id": session["session_id"],
|
| 501 |
+
},
|
| 502 |
+
{
|
| 503 |
+
"$push": {"messages": new_message},
|
| 504 |
+
"$setOnInsert": {
|
| 505 |
+
"user_id": student_id,
|
| 506 |
+
"session_id": session["session_id"],
|
| 507 |
+
"timestamp": datetime.utcnow(),
|
| 508 |
+
},
|
| 509 |
+
},
|
| 510 |
+
upsert=True,
|
| 511 |
+
)
|
| 512 |
+
chat_time_collection.update_one(
|
| 513 |
+
{"session_id": session["session_id"], "user_id": student_id, "course_id": course_id},
|
| 514 |
+
{"$set": {"end_time": datetime.now()}},
|
| 515 |
+
upsert=True
|
| 516 |
+
)
|
| 517 |
+
except Exception as db_error:
|
| 518 |
+
st.error(f"Error saving chat history: {str(db_error)}")
|
| 519 |
+
except Exception as e:
|
| 520 |
+
st.error(f"Error generating response: {str(e)}")
|
| 521 |
+
except Exception as e:
|
| 522 |
+
st.error(f"Error processing message: {str(e)}")
|
| 523 |
+
|
| 524 |
+
else:
|
| 525 |
+
# Expired session case
|
| 526 |
+
st.info("Chat session has ended. Time limit (20 minutes) reached.")
|
| 527 |
|
| 528 |
else:
|
| 529 |
st.subheader("Upload Pre-class Material")
|
|
|
|
| 568 |
st.session_state.messages = []
|
| 569 |
|
| 570 |
if st.session_state.user_type == 'student':
|
| 571 |
+
display_pre_class_quiz_tab(student_id, course_id, session["session_id"])
|
| 572 |
+
display_pre_subjective_test_tab(student_id, course_id, session["session_id"])
|
| 573 |
+
|
| 574 |
st.subheader("Create a Practice Quiz")
|
| 575 |
questions = []
|
| 576 |
quiz_id = ""
|
|
|
|
| 713 |
# except Exception as db_error:
|
| 714 |
# st.error(f"Error saving submission: {str(db_error)}")
|
| 715 |
|
| 716 |
+
def display_pre_class_quiz_tab(student_id, course_id, session_id):
|
| 717 |
+
"""Display pre-class quizzes for students"""
|
| 718 |
+
st.markdown("### Pre-class Quizzes")
|
| 719 |
+
|
| 720 |
+
# Get available quizzes
|
| 721 |
+
quizzes = quizzes_collection.find({
|
| 722 |
+
"course_id": course_id,
|
| 723 |
+
"session_id": session_id,
|
| 724 |
+
"status": "active"
|
| 725 |
+
})
|
| 726 |
+
|
| 727 |
+
quizzes = list(quizzes)
|
| 728 |
+
if not quizzes:
|
| 729 |
+
st.info("No pre-class quizzes available.")
|
| 730 |
+
return
|
| 731 |
+
|
| 732 |
+
for quiz in quizzes:
|
| 733 |
+
with st.expander(f"📝 {quiz['title']}", expanded=True):
|
| 734 |
+
# Check if already taken
|
| 735 |
+
existing_score = get_student_quiz_score(quiz["_id"], student_id)
|
| 736 |
+
|
| 737 |
+
if existing_score is not None:
|
| 738 |
+
st.success(f"Quiz completed! Score: {existing_score:.1f}%")
|
| 739 |
+
|
| 740 |
+
# Show review
|
| 741 |
+
st.subheader("Quiz Review")
|
| 742 |
+
for i, question in enumerate(quiz["questions"]):
|
| 743 |
+
st.markdown(f"**Q{i+1}:** {question['question']}")
|
| 744 |
+
for opt in question["options"]:
|
| 745 |
+
if opt.startswith(question["correct_option"]):
|
| 746 |
+
st.markdown(f"✅ {opt}")
|
| 747 |
+
else:
|
| 748 |
+
st.markdown(f"- {opt}")
|
| 749 |
+
|
| 750 |
+
else:
|
| 751 |
+
# Check time remaining
|
| 752 |
+
start_time = quiz.get('start_time')
|
| 753 |
+
if not start_time:
|
| 754 |
+
if st.button("Start Quiz", key=f"start_{quiz['_id']}"):
|
| 755 |
+
quizzes_collection.update_one(
|
| 756 |
+
{"_id": quiz["_id"]},
|
| 757 |
+
{"$set": {"start_time": datetime.now()}}
|
| 758 |
+
)
|
| 759 |
+
st.rerun()
|
| 760 |
+
else:
|
| 761 |
+
time_remaining = (start_time + timedelta(minutes=quiz['duration_minutes'])) - datetime.now()
|
| 762 |
+
|
| 763 |
+
if time_remaining.total_seconds() > 0:
|
| 764 |
+
st.info(f"Time remaining: {int(time_remaining.total_seconds() // 60)}:{int(time_remaining.total_seconds() % 60):02d}")
|
| 765 |
+
|
| 766 |
+
# Display quiz form
|
| 767 |
+
with st.form(f"pre_class_quiz_{quiz['_id']}"):
|
| 768 |
+
student_answers = {}
|
| 769 |
+
for i, question in enumerate(quiz["questions"]):
|
| 770 |
+
st.markdown(f"**Q{i+1}:** {question['question']}")
|
| 771 |
+
options = [opt for opt in question["options"]]
|
| 772 |
+
student_answers[str(i)] = st.radio(
|
| 773 |
+
f"Select answer:",
|
| 774 |
+
options=options,
|
| 775 |
+
key=f"q_{quiz['_id']}_{i}"
|
| 776 |
+
)
|
| 777 |
+
|
| 778 |
+
if st.form_submit_button("Submit Quiz"):
|
| 779 |
+
score = submit_quiz_answers(
|
| 780 |
+
quiz["_id"],
|
| 781 |
+
student_id,
|
| 782 |
+
student_answers
|
| 783 |
+
)
|
| 784 |
+
if score is not None:
|
| 785 |
+
st.success(f"Quiz submitted! Score: {score:.1f}%")
|
| 786 |
+
st.rerun()
|
| 787 |
+
else:
|
| 788 |
+
st.error("Error submitting quiz")
|
| 789 |
+
else:
|
| 790 |
+
st.error("Quiz time expired")
|
| 791 |
+
|
| 792 |
+
|
| 793 |
+
def get_chat_context(session_id):
|
| 794 |
+
"""Get context from session materials"""
|
| 795 |
+
materials = resources_collection.find({"session_id": session_id})
|
| 796 |
+
context = ""
|
| 797 |
+
for material in materials:
|
| 798 |
+
if 'text_content' in material:
|
| 799 |
+
context += f"{material['text_content']}\n"
|
| 800 |
+
return context
|
| 801 |
+
|
| 802 |
+
|
| 803 |
import requests
|
| 804 |
|
| 805 |
def get_supported_url_formats():
|
|
|
|
| 997 |
""")
|
| 998 |
return None
|
| 999 |
|
| 1000 |
+
def upload_preclass_materials(session_id, course_id, student_id):
|
| 1001 |
"""Upload pre-class materials and manage external resources for a session"""
|
| 1002 |
st.subheader("Pre-class Materials Management")
|
| 1003 |
|
| 1004 |
# Create tabs for different functionalities
|
| 1005 |
+
upload_tab, videos_tab, web_resources ,external_tab, pre_class_tab, pre_class_evaluate, pre_class_quiz = st.tabs(["Upload Materials","Upload Video Sources","Web Resources", "Resources by Perplexity", "Pre-class Questions", "Pre-class Evaluation", "Pre-class Quiz"])
|
| 1006 |
|
| 1007 |
with upload_tab:
|
| 1008 |
# Original file upload functionality
|
|
|
|
| 1016 |
if st.button("Upload Material"):
|
| 1017 |
upload_resource(course_id, session_id, file_name, uploaded_file, material_type)
|
| 1018 |
st.success("Material uploaded successfully!")
|
| 1019 |
+
|
| 1020 |
+
# Display pre-class materials
|
| 1021 |
+
# Group resources by their types
|
| 1022 |
+
grouped_resources = defaultdict(list)
|
| 1023 |
+
materials = resources_collection.find({"session_id": session_id})
|
| 1024 |
+
for material in materials:
|
| 1025 |
+
grouped_resources[material['material_type']].append(material)
|
| 1026 |
+
|
| 1027 |
+
# Display grouped resources
|
| 1028 |
+
for material_type, resources in grouped_resources.items():
|
| 1029 |
+
st.markdown(f"##### {material_type.capitalize()} Resources")
|
| 1030 |
+
for material in resources:
|
| 1031 |
+
resource_info = f"- **{material['file_name']}** ({material['file_type']})"
|
| 1032 |
+
if 'source_url' in material:
|
| 1033 |
+
resource_info += f" - [URL]({material['source_url']})"
|
| 1034 |
+
st.markdown(resource_info)
|
| 1035 |
+
|
| 1036 |
with videos_tab:
|
| 1037 |
# Upload video sources
|
| 1038 |
st.info("Upload video sources for this session.")
|
|
|
|
| 1048 |
st.info("""
|
| 1049 |
Share online resources with your students. Supported links include:
|
| 1050 |
- Google Colab notebooks
|
| 1051 |
+
- Google Slides presentations
|
| 1052 |
- Online documentation
|
| 1053 |
- Educational websites
|
| 1054 |
- Any web-based learning resource
|
|
|
|
| 1169 |
- URL: [{resource['url']}]({resource['url']})
|
| 1170 |
""")
|
| 1171 |
|
| 1172 |
+
with pre_class_tab:
|
| 1173 |
+
if st.session_state.user_type == "faculty":
|
| 1174 |
+
faculty_id = st.session_state.user_id
|
| 1175 |
+
st.subheader("Create Pre class Subjective Test")
|
| 1176 |
+
|
| 1177 |
+
# Create a form for test generation
|
| 1178 |
+
with st.form("pre_create_subjective_test_form"):
|
| 1179 |
+
test_title = st.text_input("Test Title")
|
| 1180 |
+
num_subjective_questions = st.number_input(
|
| 1181 |
+
"Number of Pre class Subjective Questions", min_value=1, value=5
|
| 1182 |
+
)
|
| 1183 |
+
generation_method = st.radio(
|
| 1184 |
+
"Question Generation Method", ["Generate from Pre-class Materials"]
|
| 1185 |
+
)
|
| 1186 |
+
generate_test_btn = st.form_submit_button("Generate Test for Pre Class")
|
| 1187 |
+
|
| 1188 |
+
# Handle test generation outside the form
|
| 1189 |
+
if generate_test_btn:
|
| 1190 |
+
if not test_title:
|
| 1191 |
+
st.error("Please enter a test title.")
|
| 1192 |
+
return
|
| 1193 |
|
| 1194 |
+
context = ""
|
| 1195 |
+
if generation_method == "Generate from Pre-class Materials":
|
| 1196 |
+
materials = resources_collection.find(
|
| 1197 |
+
{"session_id": session["session_id"]}
|
| 1198 |
+
)
|
| 1199 |
+
for material in materials:
|
| 1200 |
+
if "text_content" in material:
|
| 1201 |
+
context += material["text_content"] + "\n"
|
| 1202 |
+
|
| 1203 |
+
with st.spinner("Generating questions and synoptic..."):
|
| 1204 |
+
try:
|
| 1205 |
+
# Store generated content in session state to persist between rerenders
|
| 1206 |
+
questions = pre_generate_questions(
|
| 1207 |
+
context if context else None,
|
| 1208 |
+
num_subjective_questions,
|
| 1209 |
+
session["title"],
|
| 1210 |
+
session.get("description", ""),
|
| 1211 |
+
)
|
| 1212 |
+
|
| 1213 |
+
if questions:
|
| 1214 |
+
synoptic = generate_synoptic(
|
| 1215 |
+
questions,
|
| 1216 |
+
context if context else None,
|
| 1217 |
+
session["title"],
|
| 1218 |
+
num_subjective_questions,
|
| 1219 |
+
)
|
| 1220 |
+
|
| 1221 |
+
if synoptic:
|
| 1222 |
+
# Store in session state
|
| 1223 |
+
st.session_state.generated_questions = questions
|
| 1224 |
+
st.session_state.generated_synoptic = synoptic
|
| 1225 |
+
st.session_state.test_title = test_title
|
| 1226 |
+
|
| 1227 |
+
# Display preview
|
| 1228 |
+
st.subheader(
|
| 1229 |
+
"Preview Subjective Questions and Synoptic"
|
| 1230 |
+
)
|
| 1231 |
+
for i, (q, s) in enumerate(zip(questions, synoptic), 1):
|
| 1232 |
+
st.markdown(f"**Question {i}:** {q['question']}")
|
| 1233 |
+
with st.expander(f"View Synoptic {i}"):
|
| 1234 |
+
st.markdown(s)
|
| 1235 |
+
|
| 1236 |
+
# Save button outside the form
|
| 1237 |
+
if st.button("Pre Save Test"):
|
| 1238 |
+
test_id = pre_save_subjective_test(
|
| 1239 |
+
course_id,
|
| 1240 |
+
session["session_id"],
|
| 1241 |
+
test_title,
|
| 1242 |
+
questions,
|
| 1243 |
+
)
|
| 1244 |
+
if test_id:
|
| 1245 |
+
st.success(
|
| 1246 |
+
"Subjective test saved successfully!"
|
| 1247 |
+
)
|
| 1248 |
+
else:
|
| 1249 |
+
st.error("Error saving subjective test.")
|
| 1250 |
+
else:
|
| 1251 |
+
st.error(
|
| 1252 |
+
"Error generating synoptic answers. Please try again."
|
| 1253 |
+
)
|
| 1254 |
+
else:
|
| 1255 |
+
st.error("Error generating questions. Please try again.")
|
| 1256 |
+
except Exception as e:
|
| 1257 |
+
st.error(f"An error occurred: {str(e)}")
|
| 1258 |
+
|
| 1259 |
+
# Display previously generated test if it exists in session state
|
| 1260 |
+
elif hasattr(st.session_state, "generated_questions") and hasattr(
|
| 1261 |
+
st.session_state, "generated_synoptic"
|
| 1262 |
+
):
|
| 1263 |
+
st.subheader("Preview Subjective Questions and Synoptic")
|
| 1264 |
+
for i, (q, s) in enumerate(
|
| 1265 |
+
zip(
|
| 1266 |
+
st.session_state.generated_questions,
|
| 1267 |
+
st.session_state.generated_synoptic,
|
| 1268 |
+
),
|
| 1269 |
+
1,
|
| 1270 |
+
):
|
| 1271 |
+
st.markdown(f"**Question {i}:** {q['question']}")
|
| 1272 |
+
with st.expander(f"View Synoptic {i}"):
|
| 1273 |
+
st.markdown(s)
|
| 1274 |
+
|
| 1275 |
+
if st.button("Pre Save Test"):
|
| 1276 |
+
test_id = pre_save_subjective_test(
|
| 1277 |
+
course_id,
|
| 1278 |
+
session["session_id"],
|
| 1279 |
+
st.session_state.test_title,
|
| 1280 |
+
st.session_state.generated_questions,
|
| 1281 |
+
)
|
| 1282 |
+
if test_id:
|
| 1283 |
+
st.success("Subjective test saved successfully!")
|
| 1284 |
+
# Clear session state after successful save
|
| 1285 |
+
del st.session_state.generated_questions
|
| 1286 |
+
del st.session_state.generated_synoptic
|
| 1287 |
+
del st.session_state.test_title
|
| 1288 |
+
else:
|
| 1289 |
+
st.error("Error saving subjective test.")
|
| 1290 |
+
|
| 1291 |
+
|
| 1292 |
+
with pre_class_evaluate:
|
| 1293 |
+
from subjective_test_evaluation import pre_display_evaluation_to_faculty
|
| 1294 |
+
pre_display_evaluation_to_faculty(session["session_id"], student_id, course_id)
|
| 1295 |
+
|
| 1296 |
+
with pre_class_quiz:
|
| 1297 |
+
if st.session_state.user_type == "faculty":
|
| 1298 |
+
st.subheader("Create Pre-class Quiz")
|
| 1299 |
+
with st.form("create_pre_class_quiz_form"):
|
| 1300 |
+
quiz_title = st.text_input("Quiz Title")
|
| 1301 |
+
num_questions = st.number_input(
|
| 1302 |
+
"Number of Questions",
|
| 1303 |
+
min_value=1,
|
| 1304 |
+
max_value=20,
|
| 1305 |
+
value=5
|
| 1306 |
+
)
|
| 1307 |
+
duration = st.number_input(
|
| 1308 |
+
"Quiz Duration (minutes)",
|
| 1309 |
+
min_value=5,
|
| 1310 |
+
max_value=60,
|
| 1311 |
+
value=15
|
| 1312 |
+
)
|
| 1313 |
+
|
| 1314 |
+
# Generate quiz button
|
| 1315 |
+
if st.form_submit_button("Generate Pre-class Quiz"):
|
| 1316 |
+
if not quiz_title:
|
| 1317 |
+
st.error("Please enter a quiz title")
|
| 1318 |
+
return
|
| 1319 |
+
|
| 1320 |
+
# Get pre-class materials
|
| 1321 |
+
materials = resources_collection.find({"session_id": session_id})
|
| 1322 |
+
context = ""
|
| 1323 |
+
for material in materials:
|
| 1324 |
+
if 'text_content' in material:
|
| 1325 |
+
context += material['text_content'] + "\n"
|
| 1326 |
+
|
| 1327 |
+
if not context:
|
| 1328 |
+
st.error("No pre-class materials found")
|
| 1329 |
+
return
|
| 1330 |
+
|
| 1331 |
+
# Generate questions
|
| 1332 |
+
questions = generate_mcqs(
|
| 1333 |
+
context=context,
|
| 1334 |
+
num_questions=num_questions,
|
| 1335 |
+
session_title=session['title'],
|
| 1336 |
+
session_description=session.get('description', '')
|
| 1337 |
+
)
|
| 1338 |
+
|
| 1339 |
+
if questions:
|
| 1340 |
+
# Preview questions
|
| 1341 |
+
st.subheader("Preview Questions")
|
| 1342 |
+
for i, q in enumerate(questions, 1):
|
| 1343 |
+
st.markdown(f"**Q{i}:** {q['question']}")
|
| 1344 |
+
for opt in q['options']:
|
| 1345 |
+
st.markdown(f"- {opt}")
|
| 1346 |
+
st.markdown(f"*Correct: {q['correct_option']}*")
|
| 1347 |
+
|
| 1348 |
+
# Save quiz
|
| 1349 |
+
quiz_id = save_pre_class_quiz(
|
| 1350 |
+
course_id=course_id,
|
| 1351 |
+
session_id=session_id,
|
| 1352 |
+
title=quiz_title,
|
| 1353 |
+
questions=questions,
|
| 1354 |
+
user_id=st.session_state.user_id,
|
| 1355 |
+
duration=duration
|
| 1356 |
+
)
|
| 1357 |
+
|
| 1358 |
+
if quiz_id:
|
| 1359 |
+
st.success("Pre-class quiz created successfully!")
|
| 1360 |
+
else:
|
| 1361 |
+
st.error("Error saving quiz")
|
| 1362 |
|
| 1363 |
def extract_external_content(url, content_type):
|
| 1364 |
"""Extract content from external resources based on their type"""
|
|
|
|
| 1863 |
# st.markdown('</div>', unsafe_allow_html=True)
|
| 1864 |
|
| 1865 |
# Add save/download options
|
| 1866 |
+
# if st.button("💾 Save Outline"):
|
| 1867 |
+
# try:
|
| 1868 |
+
# courses_collection.update_one(
|
| 1869 |
+
# {"course_id": course_id, "sessions.session_id": session['session_id']},
|
| 1870 |
+
# {"$set": {"sessions.$.session_outline": st.session_state.session_outline}}
|
| 1871 |
+
# )
|
| 1872 |
+
# st.success("Outline saved successfully!")
|
| 1873 |
+
# except Exception as e:
|
| 1874 |
+
# st.error(f"Error saving outline: {str(e)}")
|
| 1875 |
+
|
| 1876 |
+
# # Download option
|
| 1877 |
+
# if st.download_button(
|
| 1878 |
+
# "📥 Download Outline",
|
| 1879 |
+
# data=json.dumps(st.session_state.session_outline, indent=2),
|
| 1880 |
+
# file_name="session_outline.json",
|
| 1881 |
+
# mime="application/json"
|
| 1882 |
+
# ):
|
| 1883 |
+
# st.success("Outline downloaded successfully!")
|
| 1884 |
+
|
| 1885 |
+
from inclass_questions import IntervalQuestionManager, display_interval_question, display_response_distribution
|
| 1886 |
def display_in_class_content(session, user_type, course_id, user_id):
|
| 1887 |
# """Display in-class activities and interactions"""
|
| 1888 |
"""Display in-class activities and interactions"""
|
|
|
|
| 1944 |
outline = generate_session_outline(session['session_id'], course_id)
|
| 1945 |
if outline:
|
| 1946 |
display_session_outline(session, outline, course_id)
|
| 1947 |
+
# st.rerun()
|
| 1948 |
print("Session outline is: ", outline)
|
| 1949 |
# Save outline to database
|
| 1950 |
+
st.success("Session outline generated and saved successfully!")
|
| 1951 |
+
courses_collection.update_one(
|
| 1952 |
+
{"course_id": course_id, "sessions.session_id": session['session_id']},
|
| 1953 |
+
{"$set": {"sessions.$.session_outline": outline}}
|
| 1954 |
+
)
|
| 1955 |
else:
|
| 1956 |
display_session_outline(session, st.session_state.session_outline, course_id)
|
| 1957 |
|
| 1958 |
+
|
| 1959 |
+
# Initialize question manager in session state
|
| 1960 |
+
if 'interval_questions' not in st.session_state:
|
| 1961 |
+
st.session_state.interval_questions = IntervalQuestionManager(session['session_id'], course_id)
|
| 1962 |
+
|
| 1963 |
+
# Faculty controls for interval questions
|
| 1964 |
+
if user_type == 'faculty':
|
| 1965 |
+
display_complete = False
|
| 1966 |
+
st.markdown(f"<div style='margin-top: 10px;'></div>", unsafe_allow_html=True)
|
| 1967 |
+
st.markdown("#### 📝 In-class Questions")
|
| 1968 |
+
|
| 1969 |
+
# Question setup
|
| 1970 |
+
if not st.session_state.interval_questions.questions:
|
| 1971 |
+
with st.form("setup_interval_questions"):
|
| 1972 |
+
num_questions = st.number_input(
|
| 1973 |
+
"Number of Questions",
|
| 1974 |
+
min_value=2,
|
| 1975 |
+
max_value=10,
|
| 1976 |
+
value=5
|
| 1977 |
+
)
|
| 1978 |
+
interval = st.number_input(
|
| 1979 |
+
"Minutes Between Questions",
|
| 1980 |
+
min_value=5,
|
| 1981 |
+
max_value=20,
|
| 1982 |
+
value=10
|
| 1983 |
+
)
|
| 1984 |
+
if st.form_submit_button("Generate Questions"):
|
| 1985 |
+
context = get_current_context(session)
|
| 1986 |
+
if st.session_state.interval_questions.initialize_questions(
|
| 1987 |
+
context,
|
| 1988 |
+
num_questions,
|
| 1989 |
+
interval
|
| 1990 |
+
):
|
| 1991 |
+
st.success("Questions generated successfully!")
|
| 1992 |
+
|
| 1993 |
+
# Display all questions overview
|
| 1994 |
+
st.markdown("##### All Questions Overview")
|
| 1995 |
+
|
| 1996 |
+
# Get all questions from the session
|
| 1997 |
+
session_data = courses_collection.find_one(
|
| 1998 |
+
{
|
| 1999 |
+
"course_id": course_id,
|
| 2000 |
+
"sessions.session_id": session['session_id']
|
| 2001 |
+
},
|
| 2002 |
+
{"sessions.$": 1}
|
| 2003 |
+
)
|
| 2004 |
+
|
| 2005 |
+
if session_data and session_data.get('sessions'):
|
| 2006 |
+
interval_questions = session_data['sessions'][0].get('in_class', {}).get('interval_questions', {})
|
| 2007 |
+
all_questions = interval_questions.get('questions', [])
|
| 2008 |
+
|
| 2009 |
+
for idx, q in enumerate(all_questions, 1):
|
| 2010 |
+
with st.expander(f"Question {idx}", expanded=False):
|
| 2011 |
+
st.markdown(f"**Type:** {q['type'].upper()}")
|
| 2012 |
+
st.markdown(f"**Question:** {q['question_text']}")
|
| 2013 |
+
|
| 2014 |
+
if q['type'] == 'mcq':
|
| 2015 |
+
st.markdown("**Options:**")
|
| 2016 |
+
for opt in q['options']:
|
| 2017 |
+
if opt == q['correct_option']:
|
| 2018 |
+
st.markdown(f"✅ {opt}")
|
| 2019 |
+
else:
|
| 2020 |
+
st.markdown(f"- {opt}")
|
| 2021 |
+
|
| 2022 |
+
st.markdown(f"**Explanation:** {q['explanation']}")
|
| 2023 |
+
|
| 2024 |
+
# Show when this question will be displayed
|
| 2025 |
+
display_time = timedelta(minutes=q['display_after'])
|
| 2026 |
+
st.info(f"Will be displayed after: {display_time}")
|
| 2027 |
+
|
| 2028 |
+
# Show submission count if any
|
| 2029 |
+
submission_count = len(q.get('submissions', []))
|
| 2030 |
+
if submission_count > 0:
|
| 2031 |
+
st.metric("Responses received", submission_count)
|
| 2032 |
+
|
| 2033 |
+
display_complete = True
|
| 2034 |
+
|
| 2035 |
+
if display_complete and st.button("Save and Start Questions"):
|
| 2036 |
+
st.rerun()
|
| 2037 |
+
|
| 2038 |
+
# Start questions button
|
| 2039 |
+
elif not st.session_state.interval_questions.start_time:
|
| 2040 |
+
if st.button("Start Interval Questions"):
|
| 2041 |
+
st.session_state.interval_questions.start_questions()
|
| 2042 |
+
st.rerun()
|
| 2043 |
+
|
| 2044 |
+
# Display current question status
|
| 2045 |
+
else:
|
| 2046 |
+
current_q = st.session_state.interval_questions.get_current_question()
|
| 2047 |
+
if current_q:
|
| 2048 |
+
st.markdown(f"**Current Question:** {current_q['question_text']}")
|
| 2049 |
+
display_response_distribution(course_id, session['session_id'], str(current_q['_id']))
|
| 2050 |
+
|
| 2051 |
+
# Student view
|
| 2052 |
+
else:
|
| 2053 |
+
current_q = st.session_state.interval_questions.get_current_question()
|
| 2054 |
+
if current_q:
|
| 2055 |
+
display_interval_question(course_id, session, current_q, user_type)
|
| 2056 |
+
|
| 2057 |
# Initialize Live Polls feature
|
| 2058 |
live_polls = LivePollFeature()
|
| 2059 |
|
|
|
|
| 3194 |
# st.markdown('</div>', unsafe_allow_html=True)
|
| 3195 |
|
| 3196 |
# Display student metrics in a grid
|
| 3197 |
+
# st.markdown('<div class="analytics-grid">', unsafe_allow_html=True)
|
| 3198 |
+
# for student in analytics["student_analytics"]:
|
| 3199 |
+
# # Apply filters
|
| 3200 |
+
# if (concept_understanding != "All" and
|
| 3201 |
+
# student["engagement_metrics"]["concept_understanding"].replace("_", " ").title() != concept_understanding):
|
| 3202 |
+
# continue
|
| 3203 |
+
|
| 3204 |
+
# participation = student["engagement_metrics"]["participation_level"] * 100
|
| 3205 |
+
# if participation_level != "All":
|
| 3206 |
+
# if participation_level == "High (>80%)" and participation <= 80:
|
| 3207 |
+
# continue
|
| 3208 |
+
# elif participation_level == "Medium (50-80%)" and (participation < 50 or participation > 80):
|
| 3209 |
+
# continue
|
| 3210 |
+
# elif participation_level == "Low (<50%)" and participation >= 50:
|
| 3211 |
+
# continue
|
| 3212 |
+
|
| 3213 |
+
# if struggling_topic != "All" and struggling_topic not in student["struggling_topics"]:
|
| 3214 |
+
# continue
|
| 3215 |
+
|
| 3216 |
+
# st.markdown(f"""
|
| 3217 |
+
# <div class="student-metrics-card">
|
| 3218 |
+
# <div class="header">
|
| 3219 |
+
# <span class="student-id">Student {student["student_id"][-6:]}</span>
|
| 3220 |
+
# </div>
|
| 3221 |
+
# <div class="metrics-grid">
|
| 3222 |
+
# <div class="metric-box">
|
| 3223 |
+
# <div class="label">Participation</div>
|
| 3224 |
+
# <div class="value">{student["engagement_metrics"]["participation_level"]*100:.1f}%</div>
|
| 3225 |
+
# </div>
|
| 3226 |
+
# <div class="metric-box">
|
| 3227 |
+
# <div class="label">Understanding</div>
|
| 3228 |
+
# <div class="value">{student["engagement_metrics"]["concept_understanding"].replace('_', ' ').title()}</div>
|
| 3229 |
+
# </div>
|
| 3230 |
+
# <div class="struggling-topics">
|
| 3231 |
+
# <div class="label">Struggling Topics: </div>
|
| 3232 |
+
# <div class="value">{", ".join(student["struggling_topics"]) if student["struggling_topics"] else "None"}</div>
|
| 3233 |
+
# </div>
|
| 3234 |
+
# <div class="recommendation-text">
|
| 3235 |
+
# {student["personalized_recommendation"]}
|
| 3236 |
+
# </div>
|
| 3237 |
+
# </div>
|
| 3238 |
+
# </div>
|
| 3239 |
+
# """, unsafe_allow_html=True)
|
| 3240 |
+
# st.markdown('</div>', unsafe_allow_html=True)
|
| 3241 |
+
st.markdown('<div class="analytics-grid">', unsafe_allow_html=True)
|
| 3242 |
+
|
| 3243 |
+
student_ids = [student["student_id"] for student in analytics["student_analytics"]]
|
| 3244 |
+
student_names = {str(student["_id"]): student["full_name"] for student in students_collection.find({"_id": {"$in": [ObjectId(sid) for sid in student_ids]}})}
|
| 3245 |
+
|
| 3246 |
+
# Display student metrics in a grid
|
| 3247 |
st.markdown('<div class="analytics-grid">', unsafe_allow_html=True)
|
| 3248 |
for student in analytics["student_analytics"]:
|
| 3249 |
# Apply filters
|
|
|
|
| 3263 |
if struggling_topic != "All" and struggling_topic not in student["struggling_topics"]:
|
| 3264 |
continue
|
| 3265 |
|
| 3266 |
+
student_name = student_names.get(student["student_id"], "Unknown Student")
|
| 3267 |
+
|
| 3268 |
st.markdown(f"""
|
| 3269 |
<div class="student-metrics-card">
|
| 3270 |
<div class="header">
|
| 3271 |
+
<span class="student-id">Student: {student_name}</span>
|
| 3272 |
</div>
|
| 3273 |
<div class="metrics-grid">
|
| 3274 |
<div class="metric-box">
|
|
|
|
| 3471 |
"Evaluate Subjective Tests"
|
| 3472 |
])
|
| 3473 |
with tabs[0]:
|
| 3474 |
+
upload_preclass_materials(session['session_id'], course_id, student_id)
|
| 3475 |
with tabs[1]:
|
| 3476 |
display_in_class_content(session, user_type, course_id, user_type)
|
| 3477 |
with tabs[2]:
|
|
|
|
| 3848 |
st.error("An error occurred while loading the tests. Please try again later.")
|
| 3849 |
print(f"Error in display_subjective_test_tab: {str(e)}")
|
| 3850 |
|
| 3851 |
+
|
| 3852 |
+
pre_subjective_test_evaluation_collection = db["pre_subjective_test_evaluation"]
|
| 3853 |
+
pre_subjective_tests_collection = db["pre_subjective_tests"]
|
| 3854 |
+
|
| 3855 |
+
def display_pre_subjective_test_tab(student_id, course_id, session_id):
|
| 3856 |
+
"""Display subjective tests and results for students"""
|
| 3857 |
+
st.markdown("### Pre-class Subjective Questions ")
|
| 3858 |
+
|
| 3859 |
+
try:
|
| 3860 |
+
print("course_id", course_id, "session_id", session_id, "student_id", student_id)
|
| 3861 |
+
subjective_tests = list(
|
| 3862 |
+
pre_subjective_tests_collection.find(
|
| 3863 |
+
{"course_id": course_id,"session_id" : str(session_id), "status": "active"}
|
| 3864 |
+
)
|
| 3865 |
+
)
|
| 3866 |
+
print("subjective_tests", subjective_tests)
|
| 3867 |
+
|
| 3868 |
+
if not subjective_tests:
|
| 3869 |
+
print("bruh")
|
| 3870 |
+
st.info("No pre subjective questions available for this session.")
|
| 3871 |
+
return
|
| 3872 |
+
|
| 3873 |
+
# Create tabs for Tests and Results
|
| 3874 |
+
test_tab, results_tab = st.tabs(["Available Tests", "Test Results"])
|
| 3875 |
+
|
| 3876 |
+
with test_tab:
|
| 3877 |
+
for test in subjective_tests:
|
| 3878 |
+
with st.expander(f"📝 {test['title']}", expanded=True):
|
| 3879 |
+
# Check for existing submission
|
| 3880 |
+
existing_submission = next(
|
| 3881 |
+
(
|
| 3882 |
+
sub
|
| 3883 |
+
for sub in test.get("submissions", [])
|
| 3884 |
+
if sub["student_id"] == str(student_id)
|
| 3885 |
+
),
|
| 3886 |
+
None,
|
| 3887 |
+
)
|
| 3888 |
+
|
| 3889 |
+
if existing_submission:
|
| 3890 |
+
st.success("Test completed! Your answers have been submitted.")
|
| 3891 |
+
st.subheader("Your Answers")
|
| 3892 |
+
for i, ans in enumerate(existing_submission["answers"]):
|
| 3893 |
+
st.markdown(
|
| 3894 |
+
f"**Question {i+1}:** {test['questions'][i]['question']}"
|
| 3895 |
+
)
|
| 3896 |
+
st.markdown(f"**Your Answer:** {ans}")
|
| 3897 |
+
st.markdown("---")
|
| 3898 |
+
else:
|
| 3899 |
+
st.write("Please write your answers:")
|
| 3900 |
+
with st.form(key=f"pre_subjective_test_form_{test['_id']}"):
|
| 3901 |
+
student_answers = []
|
| 3902 |
+
for i, question in enumerate(test["questions"]):
|
| 3903 |
+
st.markdown(
|
| 3904 |
+
f"**Question {i+1}:** {question['question']}"
|
| 3905 |
+
)
|
| 3906 |
+
answer = st.text_area(
|
| 3907 |
+
"Your answer:",
|
| 3908 |
+
key=f"q_{test['_id']}_{i}",
|
| 3909 |
+
height=200,
|
| 3910 |
+
)
|
| 3911 |
+
student_answers.append(answer)
|
| 3912 |
+
|
| 3913 |
+
if st.form_submit_button("Submit Pre Test"):
|
| 3914 |
+
if all(answer.strip() for answer in student_answers):
|
| 3915 |
+
success = submit_pre_subjective_test(
|
| 3916 |
+
test["_id"], str(student_id), student_answers
|
| 3917 |
+
)
|
| 3918 |
+
if success:
|
| 3919 |
+
st.success("Test submitted successfully!")
|
| 3920 |
+
st.rerun()
|
| 3921 |
+
else:
|
| 3922 |
+
st.error(
|
| 3923 |
+
"Error submitting test. Please try again."
|
| 3924 |
+
)
|
| 3925 |
+
else:
|
| 3926 |
+
st.error(
|
| 3927 |
+
"Please answer all questions before submitting."
|
| 3928 |
+
)
|
| 3929 |
+
|
| 3930 |
+
with results_tab:
|
| 3931 |
+
# Display results for completed tests
|
| 3932 |
+
completed_tests = [
|
| 3933 |
+
test
|
| 3934 |
+
for test in subjective_tests
|
| 3935 |
+
if any(
|
| 3936 |
+
sub["student_id"] == str(student_id)
|
| 3937 |
+
for sub in test.get("submissions", [])
|
| 3938 |
+
)
|
| 3939 |
+
]
|
| 3940 |
+
|
| 3941 |
+
if not completed_tests:
|
| 3942 |
+
st.info("You haven't completed any tests yet.")
|
| 3943 |
+
return
|
| 3944 |
+
|
| 3945 |
+
# Create a selectbox for choosing which test results to view
|
| 3946 |
+
test_options = {
|
| 3947 |
+
f"{test['title']} (Submitted: {next(sub['submitted_at'].strftime('%Y-%m-%d') for sub in test['submissions'] if sub['student_id'] == str(student_id))})": test[
|
| 3948 |
+
"_id"
|
| 3949 |
+
]
|
| 3950 |
+
for test in completed_tests
|
| 3951 |
+
}
|
| 3952 |
+
|
| 3953 |
+
selected_test = st.selectbox(
|
| 3954 |
+
"Select a test to view results:", options=list(test_options.keys())
|
| 3955 |
+
)
|
| 3956 |
+
|
| 3957 |
+
if selected_test:
|
| 3958 |
+
test_id = test_options[selected_test]
|
| 3959 |
+
display_pre_test_results(test_id, student_id)
|
| 3960 |
+
|
| 3961 |
+
except Exception as e:
|
| 3962 |
+
st.error("An error occurred while loading the tests. Please try again later.")
|
| 3963 |
+
print(f"Error in display_subjective_test_tab: {str(e)}")
|
| 3964 |
+
|
| 3965 |
def display_test_results(test_id, student_id):
|
| 3966 |
"""
|
| 3967 |
Display test results and analysis for a student
|
session_page_alt.py
CHANGED
|
@@ -18,6 +18,7 @@ import os
|
|
| 18 |
from pymongo import MongoClient
|
| 19 |
from gen_mcqs import (
|
| 20 |
generate_mcqs,
|
|
|
|
| 21 |
save_quiz,
|
| 22 |
save_surprise_quiz,
|
| 23 |
quizzes_collection,
|
|
@@ -282,6 +283,11 @@ def display_preclass_content(session, student_id, course_id):
|
|
| 282 |
if 'messages' not in st.session_state:
|
| 283 |
st.session_state.messages = []
|
| 284 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
# Chat input
|
| 286 |
# Add a check, if materials are available, only then show the chat input
|
| 287 |
if st.session_state.user_type == "student":
|
|
@@ -290,104 +296,58 @@ def display_preclass_content(session, student_id, course_id):
|
|
| 290 |
chat_time = chat_time_collection.find_one(
|
| 291 |
{"session_id": session["session_id"], "user_id": student_id, "course_id": course_id}
|
| 292 |
)
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
if st.button("Start Chat Session (20 minutes)"):
|
| 297 |
-
st.session_state.chat_session_start =
|
|
|
|
|
|
|
|
|
|
| 298 |
chat_time_collection.update_one(
|
| 299 |
-
{
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
upsert=True
|
| 302 |
)
|
| 303 |
-
st.session_state.chat_session_active = True
|
| 304 |
st.rerun()
|
| 305 |
-
# else:
|
| 306 |
-
# st.info("Chat session is now disabled.")
|
| 307 |
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
if chat_time is None or "start_time" not in chat_time or (chat_time["start_time"] + timedelta(minutes=20)) <= datetime.now():
|
| 314 |
-
cond = True
|
| 315 |
-
|
| 316 |
-
if st.session_state.chat_session_active and st.session_state.chat_session_start and not cond:
|
| 317 |
-
current_time = datetime.now()
|
| 318 |
-
time_elapsed = current_time - chat_time["start_time"]
|
| 319 |
-
time_remaining = timedelta(minutes=20) - time_elapsed
|
| 320 |
-
|
| 321 |
-
if time_remaining.total_seconds() > 0:
|
| 322 |
-
# Display remaining time
|
| 323 |
-
st.info(f"Time remaining: {int(time_remaining.total_seconds() // 60)} minutes and {int(time_remaining.total_seconds() % 60)} seconds")
|
| 324 |
-
|
| 325 |
-
# Show chat input
|
| 326 |
-
if prompt := st.chat_input("Ask a question about Pre-class Materials"):
|
| 327 |
-
st.session_state.messages.append({"role": "user", "content": prompt})
|
| 328 |
-
|
| 329 |
-
# Display User Message
|
| 330 |
-
with st.chat_message("user"):
|
| 331 |
-
st.markdown(prompt)
|
| 332 |
-
|
| 333 |
-
# Get document context
|
| 334 |
-
context = ""
|
| 335 |
-
print("Session ID is: ", session["session_id"])
|
| 336 |
-
materials = resources_collection.find(
|
| 337 |
-
{"session_id": session["session_id"]}
|
| 338 |
-
)
|
| 339 |
-
print(materials)
|
| 340 |
-
context = ""
|
| 341 |
-
vector_data = None
|
| 342 |
-
|
| 343 |
-
# for material in materials:
|
| 344 |
-
# print(material)
|
| 345 |
-
context = ""
|
| 346 |
-
for material in materials:
|
| 347 |
-
resource_id = material["_id"]
|
| 348 |
-
print("Supposed Resource ID is: ", resource_id)
|
| 349 |
-
vector_data = vectors_collection.find_one(
|
| 350 |
-
{"resource_id": resource_id}
|
| 351 |
-
)
|
| 352 |
-
# print(vector_data)
|
| 353 |
-
if vector_data and "text" in vector_data:
|
| 354 |
-
context += vector_data["text"] + "\n"
|
| 355 |
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
try:
|
| 361 |
-
# Generate response using Gemini
|
| 362 |
-
# context_prompt = f"""
|
| 363 |
-
# Based on the following context, answer the user's question:
|
| 364 |
-
|
| 365 |
-
# Context:
|
| 366 |
-
# {context}
|
| 367 |
-
|
| 368 |
-
# Question: {prompt}
|
| 369 |
-
|
| 370 |
-
# Please provide a clear and concise answer based only on the information provided in the context.
|
| 371 |
-
# """
|
| 372 |
-
# context_prompt = f"""
|
| 373 |
-
# You are a highly intelligent and resourceful assistant capable of synthesizing information from the provided context.
|
| 374 |
-
|
| 375 |
-
# Context:
|
| 376 |
-
# {context}
|
| 377 |
-
|
| 378 |
-
# Instructions:
|
| 379 |
-
# 1. Base your answers primarily on the given context.
|
| 380 |
-
# 2. If the answer to the user's question is not explicitly in the context but can be inferred or synthesized from the information provided, do so thoughtfully.
|
| 381 |
-
# 3. Only use external knowledge or web assistance when:
|
| 382 |
-
# - The context lacks sufficient information, and
|
| 383 |
-
# - The question requires knowledge beyond what can be reasonably inferred from the context.
|
| 384 |
-
# 4. Clearly state if you are relying on web assistance for any part of your answer.
|
| 385 |
-
# 5. Do not respond with a negative. If the answer is not in the context, provide a thoughtful response based on the information available on the web about it.
|
| 386 |
-
|
| 387 |
-
# Question: {prompt}
|
| 388 |
-
|
| 389 |
-
# Please provide a clear and comprehensive answer based on the above instructions.
|
| 390 |
-
# """
|
| 391 |
context_prompt = f"""
|
| 392 |
You are a highly intelligent and resourceful assistant capable of synthesizing information from the provided context and external sources.
|
| 393 |
|
|
@@ -450,10 +410,178 @@ def display_preclass_content(session, student_id, course_id):
|
|
| 450 |
st.error(f"Error saving chat history: {str(db_error)}")
|
| 451 |
except Exception as e:
|
| 452 |
st.error(f"Error generating response: {str(e)}")
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 457 |
|
| 458 |
else:
|
| 459 |
st.subheader("Upload Pre-class Material")
|
|
@@ -498,6 +626,10 @@ def display_preclass_content(session, student_id, course_id):
|
|
| 498 |
st.session_state.messages = []
|
| 499 |
|
| 500 |
if st.session_state.user_type == 'student':
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
st.subheader("Create a Practice Quiz")
|
| 502 |
questions = []
|
| 503 |
quiz_id = ""
|
|
@@ -640,12 +772,19 @@ def display_preclass_content(session, student_id, course_id):
|
|
| 640 |
# except Exception as db_error:
|
| 641 |
# st.error(f"Error saving submission: {str(db_error)}")
|
| 642 |
|
| 643 |
-
if st.session_state.user_type == "student":
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 649 |
|
| 650 |
import requests
|
| 651 |
|
|
@@ -849,7 +988,7 @@ def upload_preclass_materials(session_id, course_id, student_id):
|
|
| 849 |
st.subheader("Pre-class Materials Management")
|
| 850 |
|
| 851 |
# Create tabs for different functionalities
|
| 852 |
-
upload_tab, videos_tab, web_resources ,external_tab, pre_class_evaluate = st.tabs(["Upload Materials","Upload Video Sources","Web Resources", "Resources by Perplexity", "Pre-class Evaluation"])
|
| 853 |
|
| 854 |
with upload_tab:
|
| 855 |
# Original file upload functionality
|
|
@@ -863,6 +1002,23 @@ def upload_preclass_materials(session_id, course_id, student_id):
|
|
| 863 |
if st.button("Upload Material"):
|
| 864 |
upload_resource(course_id, session_id, file_name, uploaded_file, material_type)
|
| 865 |
st.success("Material uploaded successfully!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 866 |
with videos_tab:
|
| 867 |
# Upload video sources
|
| 868 |
st.info("Upload video sources for this session.")
|
|
@@ -878,7 +1034,7 @@ def upload_preclass_materials(session_id, course_id, student_id):
|
|
| 878 |
st.info("""
|
| 879 |
Share online resources with your students. Supported links include:
|
| 880 |
- Google Colab notebooks
|
| 881 |
-
- Google Slides
|
| 882 |
- Online documentation
|
| 883 |
- Educational websites
|
| 884 |
- Any web-based learning resource
|
|
@@ -999,25 +1155,274 @@ def upload_preclass_materials(session_id, course_id, student_id):
|
|
| 999 |
- URL: [{resource['url']}]({resource['url']})
|
| 1000 |
""")
|
| 1001 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1002 |
with pre_class_evaluate:
|
| 1003 |
from subjective_test_evaluation import pre_display_evaluation_to_faculty
|
| 1004 |
pre_display_evaluation_to_faculty(session["session_id"], student_id, course_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1005 |
|
| 1006 |
-
|
| 1007 |
-
|
| 1008 |
-
|
| 1009 |
-
|
| 1010 |
-
|
| 1011 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1012 |
|
| 1013 |
-
# Display grouped resources
|
| 1014 |
-
for material_type, resources in grouped_resources.items():
|
| 1015 |
-
st.markdown(f"##### {material_type.capitalize()} Resources")
|
| 1016 |
-
for material in resources:
|
| 1017 |
-
resource_info = f"- **{material['file_name']}** ({material['file_type']})"
|
| 1018 |
-
if 'source_url' in material:
|
| 1019 |
-
resource_info += f" - [URL]({material['source_url']})"
|
| 1020 |
-
st.markdown(resource_info)
|
| 1021 |
|
| 1022 |
def extract_external_content(url, content_type):
|
| 1023 |
"""Extract content from external resources based on their type"""
|
|
@@ -1292,7 +1697,7 @@ def display_in_class_content(session, user_type, course_id, user_id):
|
|
| 1292 |
if user_type == "faculty":
|
| 1293 |
faculty_id = st.session_state.user_id
|
| 1294 |
|
| 1295 |
-
st.
|
| 1296 |
|
| 1297 |
questions = []
|
| 1298 |
with st.form("create_surprise_quiz_form"):
|
|
@@ -1307,7 +1712,7 @@ def display_in_class_content(session, user_type, course_id, user_id):
|
|
| 1307 |
# Option to choose quiz generation method
|
| 1308 |
generation_method = st.radio(
|
| 1309 |
"Question Generation Method",
|
| 1310 |
-
["Generate from Pre-class Materials"
|
| 1311 |
)
|
| 1312 |
|
| 1313 |
submit_quiz = st.form_submit_button("Generate Surprise Quiz")
|
|
@@ -2477,6 +2882,12 @@ def display_preclass_analytics2(session, course_id):
|
|
| 2477 |
|
| 2478 |
# Display student metrics in a grid
|
| 2479 |
st.markdown('<div class="analytics-grid">', unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2480 |
for student in analytics["student_analytics"]:
|
| 2481 |
# Apply filters
|
| 2482 |
if (concept_understanding != "All" and
|
|
@@ -2495,10 +2906,12 @@ def display_preclass_analytics2(session, course_id):
|
|
| 2495 |
if struggling_topic != "All" and struggling_topic not in student["struggling_topics"]:
|
| 2496 |
continue
|
| 2497 |
|
|
|
|
|
|
|
| 2498 |
st.markdown(f"""
|
| 2499 |
<div class="student-metrics-card">
|
| 2500 |
<div class="header">
|
| 2501 |
-
<span class="student-id">Student {
|
| 2502 |
</div>
|
| 2503 |
<div class="metrics-grid">
|
| 2504 |
<div class="metric-box">
|
|
@@ -3270,7 +3683,7 @@ def display_subjective_test_tab(student_id, course_id, session_id):
|
|
| 3270 |
|
| 3271 |
def display_pre_subjective_test_tab(student_id, course_id, session_id):
|
| 3272 |
"""Display subjective tests and results for students"""
|
| 3273 |
-
st.
|
| 3274 |
|
| 3275 |
try:
|
| 3276 |
print("course_id", course_id, "session_id", session_id, "student_id", student_id)
|
|
@@ -3283,7 +3696,7 @@ def display_pre_subjective_test_tab(student_id, course_id, session_id):
|
|
| 3283 |
|
| 3284 |
if not subjective_tests:
|
| 3285 |
print("bruh")
|
| 3286 |
-
st.info("No pre subjective
|
| 3287 |
return
|
| 3288 |
|
| 3289 |
# Create tabs for Tests and Results
|
|
|
|
| 18 |
from pymongo import MongoClient
|
| 19 |
from gen_mcqs import (
|
| 20 |
generate_mcqs,
|
| 21 |
+
save_pre_class_quiz,
|
| 22 |
save_quiz,
|
| 23 |
save_surprise_quiz,
|
| 24 |
quizzes_collection,
|
|
|
|
| 283 |
if 'messages' not in st.session_state:
|
| 284 |
st.session_state.messages = []
|
| 285 |
|
| 286 |
+
if 'chat_session_active' not in st.session_state:
|
| 287 |
+
st.session_state.chat_session_active = False
|
| 288 |
+
if 'chat_session_start' not in st.session_state:
|
| 289 |
+
st.session_state.chat_session_start = None
|
| 290 |
+
|
| 291 |
# Chat input
|
| 292 |
# Add a check, if materials are available, only then show the chat input
|
| 293 |
if st.session_state.user_type == "student":
|
|
|
|
| 296 |
chat_time = chat_time_collection.find_one(
|
| 297 |
{"session_id": session["session_id"], "user_id": student_id, "course_id": course_id}
|
| 298 |
)
|
| 299 |
+
|
| 300 |
+
# Calculate session status and time remaining
|
| 301 |
+
current_time = datetime.now()
|
| 302 |
+
chat_active = False
|
| 303 |
+
time_remaining = None
|
| 304 |
+
|
| 305 |
+
if chat_time and "start_time" in chat_time:
|
| 306 |
+
session_end_time = chat_time["start_time"] + timedelta(minutes=20)
|
| 307 |
+
time_remaining = session_end_time - current_time
|
| 308 |
+
chat_active = time_remaining.total_seconds() > 0
|
| 309 |
+
|
| 310 |
+
# Display appropriate interface based on session status
|
| 311 |
+
if not chat_time or "start_time" not in chat_time:
|
| 312 |
+
# New session case
|
| 313 |
if st.button("Start Chat Session (20 minutes)"):
|
| 314 |
+
st.session_state.chat_session_start = current_time
|
| 315 |
+
st.session_state.chat_session_active = True
|
| 316 |
+
|
| 317 |
+
# Update database
|
| 318 |
chat_time_collection.update_one(
|
| 319 |
+
{
|
| 320 |
+
"session_id": session["session_id"],
|
| 321 |
+
"user_id": student_id,
|
| 322 |
+
"course_id": course_id
|
| 323 |
+
},
|
| 324 |
+
{
|
| 325 |
+
"$set": {
|
| 326 |
+
"start_time": current_time,
|
| 327 |
+
"end_time": current_time + timedelta(minutes=20)
|
| 328 |
+
}
|
| 329 |
+
},
|
| 330 |
upsert=True
|
| 331 |
)
|
|
|
|
| 332 |
st.rerun()
|
|
|
|
|
|
|
| 333 |
|
| 334 |
+
elif chat_active:
|
| 335 |
+
# Active session case
|
| 336 |
+
minutes = int(time_remaining.total_seconds() // 60)
|
| 337 |
+
seconds = int(time_remaining.total_seconds() % 60)
|
| 338 |
+
st.info(f"⏱️ Time remaining: {minutes:02d}:{seconds:02d}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
|
| 340 |
+
# Show chat input
|
| 341 |
+
if prompt := st.chat_input("Ask a question about Pre-class Materials"):
|
| 342 |
+
st.session_state.messages.append({"role": "user", "content": prompt})
|
| 343 |
|
| 344 |
+
# Display User Message
|
| 345 |
+
with st.chat_message("user"):
|
| 346 |
+
st.markdown(prompt)
|
| 347 |
+
|
| 348 |
+
try:
|
| 349 |
+
context = get_chat_context(session['session_id'])
|
| 350 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
context_prompt = f"""
|
| 352 |
You are a highly intelligent and resourceful assistant capable of synthesizing information from the provided context and external sources.
|
| 353 |
|
|
|
|
| 410 |
st.error(f"Error saving chat history: {str(db_error)}")
|
| 411 |
except Exception as e:
|
| 412 |
st.error(f"Error generating response: {str(e)}")
|
| 413 |
+
except Exception as e:
|
| 414 |
+
st.error(f"Error processing message: {str(e)}")
|
| 415 |
+
|
| 416 |
+
else:
|
| 417 |
+
# Expired session case
|
| 418 |
+
st.info("Chat session has ended. Time limit (20 minutes) reached.")
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
# start_time = chat_time["start_time"] if chat_time and "start_time" in chat_time else None
|
| 422 |
+
# print("Start Time is: ", start_time)
|
| 423 |
+
# if start_time is None:
|
| 424 |
+
# if st.button("Start Chat Session (20 minutes)"):
|
| 425 |
+
# st.session_state.chat_session_start = datetime.now()
|
| 426 |
+
# chat_time_collection.update_one(
|
| 427 |
+
# {"session_id": session["session_id"], "user_id": student_id, "course_id": course_id},
|
| 428 |
+
# {"$set": {"start_time": st.session_state.chat_session_start}},
|
| 429 |
+
# upsert=True
|
| 430 |
+
# )
|
| 431 |
+
# st.session_state.chat_session_active = True
|
| 432 |
+
# st.rerun()
|
| 433 |
+
# else:
|
| 434 |
+
# st.info("Chat session is now disabled.")
|
| 435 |
+
|
| 436 |
+
# # Check if chat session is active and within time limit
|
| 437 |
+
# chat_time = chat_time_collection.find_one(
|
| 438 |
+
# {"session_id": session["session_id"], "user_id": student_id, "course_id": course_id}
|
| 439 |
+
# )
|
| 440 |
+
# cond = False
|
| 441 |
+
# if chat_time is None or "start_time" not in chat_time or (chat_time["start_time"] + timedelta(minutes=20)) <= datetime.now():
|
| 442 |
+
# cond = True
|
| 443 |
+
|
| 444 |
+
# if st.session_state.chat_session_active and st.session_state.chat_session_start and not cond:
|
| 445 |
+
# current_time = datetime.now()
|
| 446 |
+
# time_elapsed = current_time - chat_time["start_time"]
|
| 447 |
+
# time_remaining = timedelta(minutes=20) - time_elapsed
|
| 448 |
+
|
| 449 |
+
# if time_remaining.total_seconds() > 0:
|
| 450 |
+
# # Display remaining time
|
| 451 |
+
# st.info(f"Time remaining: {int(time_remaining.total_seconds() // 60)} minutes and {int(time_remaining.total_seconds() % 60)} seconds")
|
| 452 |
+
|
| 453 |
+
# # Show chat input
|
| 454 |
+
# if prompt := st.chat_input("Ask a question about Pre-class Materials"):
|
| 455 |
+
# st.session_state.messages.append({"role": "user", "content": prompt})
|
| 456 |
+
|
| 457 |
+
# # Display User Message
|
| 458 |
+
# with st.chat_message("user"):
|
| 459 |
+
# st.markdown(prompt)
|
| 460 |
+
|
| 461 |
+
# # Get document context
|
| 462 |
+
# context = ""
|
| 463 |
+
# print("Session ID is: ", session["session_id"])
|
| 464 |
+
# materials = resources_collection.find(
|
| 465 |
+
# {"session_id": session["session_id"]}
|
| 466 |
+
# )
|
| 467 |
+
# print(materials)
|
| 468 |
+
# context = ""
|
| 469 |
+
# vector_data = None
|
| 470 |
+
|
| 471 |
+
# # for material in materials:
|
| 472 |
+
# # print(material)
|
| 473 |
+
# context = ""
|
| 474 |
+
# for material in materials:
|
| 475 |
+
# resource_id = material["_id"]
|
| 476 |
+
# print("Supposed Resource ID is: ", resource_id)
|
| 477 |
+
# vector_data = vectors_collection.find_one(
|
| 478 |
+
# {"resource_id": resource_id}
|
| 479 |
+
# )
|
| 480 |
+
# # print(vector_data)
|
| 481 |
+
# if vector_data and "text" in vector_data:
|
| 482 |
+
# context += vector_data["text"] + "\n"
|
| 483 |
+
|
| 484 |
+
# if not vector_data:
|
| 485 |
+
# st.error("No Pre-class materials found for this session.")
|
| 486 |
+
# return
|
| 487 |
+
|
| 488 |
+
# try:
|
| 489 |
+
# # Generate response using Gemini
|
| 490 |
+
# # context_prompt = f"""
|
| 491 |
+
# # Based on the following context, answer the user's question:
|
| 492 |
+
|
| 493 |
+
# # Context:
|
| 494 |
+
# # {context}
|
| 495 |
+
|
| 496 |
+
# # Question: {prompt}
|
| 497 |
+
|
| 498 |
+
# # Please provide a clear and concise answer based only on the information provided in the context.
|
| 499 |
+
# # """
|
| 500 |
+
# # context_prompt = f"""
|
| 501 |
+
# # You are a highly intelligent and resourceful assistant capable of synthesizing information from the provided context.
|
| 502 |
+
|
| 503 |
+
# # Context:
|
| 504 |
+
# # {context}
|
| 505 |
+
|
| 506 |
+
# # Instructions:
|
| 507 |
+
# # 1. Base your answers primarily on the given context.
|
| 508 |
+
# # 2. If the answer to the user's question is not explicitly in the context but can be inferred or synthesized from the information provided, do so thoughtfully.
|
| 509 |
+
# # 3. Only use external knowledge or web assistance when:
|
| 510 |
+
# # - The context lacks sufficient information, and
|
| 511 |
+
# # - The question requires knowledge beyond what can be reasonably inferred from the context.
|
| 512 |
+
# # 4. Clearly state if you are relying on web assistance for any part of your answer.
|
| 513 |
+
# # 5. Do not respond with a negative. If the answer is not in the context, provide a thoughtful response based on the information available on the web about it.
|
| 514 |
+
|
| 515 |
+
# # Question: {prompt}
|
| 516 |
+
|
| 517 |
+
# # Please provide a clear and comprehensive answer based on the above instructions.
|
| 518 |
+
# # """
|
| 519 |
+
# context_prompt = f"""
|
| 520 |
+
# You are a highly intelligent and resourceful assistant capable of synthesizing information from the provided context and external sources.
|
| 521 |
+
|
| 522 |
+
# Context:
|
| 523 |
+
# {context}
|
| 524 |
+
|
| 525 |
+
# Instructions:
|
| 526 |
+
# 1. Base your answers on the provided context wherever possible.
|
| 527 |
+
# 2. If the answer to the user's question is not explicitly in the context:
|
| 528 |
+
# - Use external knowledge or web assistance to provide a clear and accurate response.
|
| 529 |
+
# 3. Do not respond negatively. If the answer is not in the context, use web assistance or your knowledge to generate a thoughtful response.
|
| 530 |
+
# 4. Clearly state if part of your response relies on web assistance.
|
| 531 |
+
|
| 532 |
+
# Question: {prompt}
|
| 533 |
+
|
| 534 |
+
# Please provide a clear and comprehensive answer based on the above instructions.
|
| 535 |
+
# """
|
| 536 |
+
|
| 537 |
+
# response = model.generate_content(context_prompt)
|
| 538 |
+
# if not response or not response.text:
|
| 539 |
+
# st.error("No response received from the model")
|
| 540 |
+
# return
|
| 541 |
+
|
| 542 |
+
# assistant_response = response.text
|
| 543 |
+
# # Display Assistant Response
|
| 544 |
+
# with st.chat_message("assistant"):
|
| 545 |
+
# st.markdown(assistant_response)
|
| 546 |
+
|
| 547 |
+
# # Build the message
|
| 548 |
+
# new_message = {
|
| 549 |
+
# "prompt": prompt,
|
| 550 |
+
# "response": assistant_response,
|
| 551 |
+
# "timestamp": datetime.utcnow(),
|
| 552 |
+
# }
|
| 553 |
+
# st.session_state.messages.append(new_message)
|
| 554 |
+
|
| 555 |
+
# # Update database
|
| 556 |
+
# try:
|
| 557 |
+
# chat_history_collection.update_one(
|
| 558 |
+
# {
|
| 559 |
+
# "user_id": student_id,
|
| 560 |
+
# "session_id": session["session_id"],
|
| 561 |
+
# },
|
| 562 |
+
# {
|
| 563 |
+
# "$push": {"messages": new_message},
|
| 564 |
+
# "$setOnInsert": {
|
| 565 |
+
# "user_id": student_id,
|
| 566 |
+
# "session_id": session["session_id"],
|
| 567 |
+
# "timestamp": datetime.utcnow(),
|
| 568 |
+
# },
|
| 569 |
+
# },
|
| 570 |
+
# upsert=True,
|
| 571 |
+
# )
|
| 572 |
+
# chat_time_collection.update_one(
|
| 573 |
+
# {"session_id": session["session_id"], "user_id": student_id, "course_id": course_id},
|
| 574 |
+
# {"$set": {"end_time": datetime.now()}},
|
| 575 |
+
# upsert=True
|
| 576 |
+
# )
|
| 577 |
+
# except Exception as db_error:
|
| 578 |
+
# st.error(f"Error saving chat history: {str(db_error)}")
|
| 579 |
+
# except Exception as e:
|
| 580 |
+
# st.error(f"Error generating response: {str(e)}")
|
| 581 |
+
# else:
|
| 582 |
+
# st.warning("Chat session has expired. The 20-minute time limit has been reached.")
|
| 583 |
+
# st.session_state.chat_session_active = False
|
| 584 |
+
# st.session_state.chat_session_start = None
|
| 585 |
|
| 586 |
else:
|
| 587 |
st.subheader("Upload Pre-class Material")
|
|
|
|
| 626 |
st.session_state.messages = []
|
| 627 |
|
| 628 |
if st.session_state.user_type == 'student':
|
| 629 |
+
|
| 630 |
+
display_pre_class_quiz_tab(student_id, course_id, session["session_id"])
|
| 631 |
+
display_pre_subjective_test_tab(student_id, course_id, session["session_id"])
|
| 632 |
+
|
| 633 |
st.subheader("Create a Practice Quiz")
|
| 634 |
questions = []
|
| 635 |
quiz_id = ""
|
|
|
|
| 772 |
# except Exception as db_error:
|
| 773 |
# st.error(f"Error saving submission: {str(db_error)}")
|
| 774 |
|
| 775 |
+
# if st.session_state.user_type == "student":
|
| 776 |
+
# # display_pre_subjective_test_tab(session, course_id, session_id)
|
| 777 |
+
# display_pre_subjective_test_tab(student_id, course_id, session["session_id"]) # Added this line
|
| 778 |
+
# pass
|
| 779 |
+
|
| 780 |
+
def get_chat_context(session_id):
|
| 781 |
+
"""Get context from session materials"""
|
| 782 |
+
materials = resources_collection.find({"session_id": session_id})
|
| 783 |
+
context = ""
|
| 784 |
+
for material in materials:
|
| 785 |
+
if 'text_content' in material:
|
| 786 |
+
context += f"{material['text_content']}\n"
|
| 787 |
+
return context
|
| 788 |
|
| 789 |
import requests
|
| 790 |
|
|
|
|
| 988 |
st.subheader("Pre-class Materials Management")
|
| 989 |
|
| 990 |
# Create tabs for different functionalities
|
| 991 |
+
upload_tab, videos_tab, web_resources ,external_tab, pre_class_tab, pre_class_evaluate, pre_class_quiz = st.tabs(["Upload Materials","Upload Video Sources","Web Resources", "Resources by Perplexity", "Pre-class Questions", "Pre-class Evaluation", "Pre-class Quiz"])
|
| 992 |
|
| 993 |
with upload_tab:
|
| 994 |
# Original file upload functionality
|
|
|
|
| 1002 |
if st.button("Upload Material"):
|
| 1003 |
upload_resource(course_id, session_id, file_name, uploaded_file, material_type)
|
| 1004 |
st.success("Material uploaded successfully!")
|
| 1005 |
+
|
| 1006 |
+
# Display pre-class materials
|
| 1007 |
+
# Group resources by their types
|
| 1008 |
+
grouped_resources = defaultdict(list)
|
| 1009 |
+
materials = resources_collection.find({"session_id": session_id})
|
| 1010 |
+
for material in materials:
|
| 1011 |
+
grouped_resources[material['material_type']].append(material)
|
| 1012 |
+
|
| 1013 |
+
# Display grouped resources
|
| 1014 |
+
for material_type, resources in grouped_resources.items():
|
| 1015 |
+
st.markdown(f"##### {material_type.capitalize()} Resources")
|
| 1016 |
+
for material in resources:
|
| 1017 |
+
resource_info = f"- **{material['file_name']}** ({material['file_type']})"
|
| 1018 |
+
if 'source_url' in material:
|
| 1019 |
+
resource_info += f" - [URL]({material['source_url']})"
|
| 1020 |
+
st.markdown(resource_info)
|
| 1021 |
+
|
| 1022 |
with videos_tab:
|
| 1023 |
# Upload video sources
|
| 1024 |
st.info("Upload video sources for this session.")
|
|
|
|
| 1034 |
st.info("""
|
| 1035 |
Share online resources with your students. Supported links include:
|
| 1036 |
- Google Colab notebooks
|
| 1037 |
+
- Google Slides presentations
|
| 1038 |
- Online documentation
|
| 1039 |
- Educational websites
|
| 1040 |
- Any web-based learning resource
|
|
|
|
| 1155 |
- URL: [{resource['url']}]({resource['url']})
|
| 1156 |
""")
|
| 1157 |
|
| 1158 |
+
with pre_class_tab:
|
| 1159 |
+
if st.session_state.user_type == "faculty":
|
| 1160 |
+
faculty_id = st.session_state.user_id
|
| 1161 |
+
st.subheader("Create Pre class Subjective Test")
|
| 1162 |
+
|
| 1163 |
+
# Create a form for test generation
|
| 1164 |
+
with st.form("pre_create_subjective_test_form"):
|
| 1165 |
+
test_title = st.text_input("Test Title")
|
| 1166 |
+
num_subjective_questions = st.number_input(
|
| 1167 |
+
"Number of Pre class Subjective Questions", min_value=1, value=5
|
| 1168 |
+
)
|
| 1169 |
+
generation_method = st.radio(
|
| 1170 |
+
"Question Generation Method", ["Generate from Pre-class Materials"]
|
| 1171 |
+
)
|
| 1172 |
+
generate_test_btn = st.form_submit_button("Generate Test for Pre Class")
|
| 1173 |
+
|
| 1174 |
+
# Handle test generation outside the form
|
| 1175 |
+
if generate_test_btn:
|
| 1176 |
+
if not test_title:
|
| 1177 |
+
st.error("Please enter a test title.")
|
| 1178 |
+
return
|
| 1179 |
+
|
| 1180 |
+
context = ""
|
| 1181 |
+
if generation_method == "Generate from Pre-class Materials":
|
| 1182 |
+
materials = resources_collection.find(
|
| 1183 |
+
{"session_id": session["session_id"]}
|
| 1184 |
+
)
|
| 1185 |
+
for material in materials:
|
| 1186 |
+
if "text_content" in material:
|
| 1187 |
+
context += material["text_content"] + "\n"
|
| 1188 |
+
|
| 1189 |
+
with st.spinner("Generating questions and synoptic..."):
|
| 1190 |
+
try:
|
| 1191 |
+
# Store generated content in session state to persist between rerenders
|
| 1192 |
+
questions = pre_generate_questions(
|
| 1193 |
+
context if context else None,
|
| 1194 |
+
num_subjective_questions,
|
| 1195 |
+
session["title"],
|
| 1196 |
+
session.get("description", ""),
|
| 1197 |
+
)
|
| 1198 |
+
|
| 1199 |
+
if questions:
|
| 1200 |
+
synoptic = generate_synoptic(
|
| 1201 |
+
questions,
|
| 1202 |
+
context if context else None,
|
| 1203 |
+
session["title"],
|
| 1204 |
+
num_subjective_questions,
|
| 1205 |
+
)
|
| 1206 |
+
|
| 1207 |
+
if synoptic:
|
| 1208 |
+
# Store in session state
|
| 1209 |
+
st.session_state.generated_questions = questions
|
| 1210 |
+
st.session_state.generated_synoptic = synoptic
|
| 1211 |
+
st.session_state.test_title = test_title
|
| 1212 |
+
|
| 1213 |
+
# Display preview
|
| 1214 |
+
st.subheader(
|
| 1215 |
+
"Preview Subjective Questions and Synoptic"
|
| 1216 |
+
)
|
| 1217 |
+
for i, (q, s) in enumerate(zip(questions, synoptic), 1):
|
| 1218 |
+
st.markdown(f"**Question {i}:** {q['question']}")
|
| 1219 |
+
with st.expander(f"View Synoptic {i}"):
|
| 1220 |
+
st.markdown(s)
|
| 1221 |
+
|
| 1222 |
+
# Save button outside the form
|
| 1223 |
+
if st.button("Pre Save Test"):
|
| 1224 |
+
test_id = pre_save_subjective_test(
|
| 1225 |
+
course_id,
|
| 1226 |
+
session["session_id"],
|
| 1227 |
+
test_title,
|
| 1228 |
+
questions,
|
| 1229 |
+
)
|
| 1230 |
+
if test_id:
|
| 1231 |
+
st.success(
|
| 1232 |
+
"Subjective test saved successfully!"
|
| 1233 |
+
)
|
| 1234 |
+
else:
|
| 1235 |
+
st.error("Error saving subjective test.")
|
| 1236 |
+
else:
|
| 1237 |
+
st.error(
|
| 1238 |
+
"Error generating synoptic answers. Please try again."
|
| 1239 |
+
)
|
| 1240 |
+
else:
|
| 1241 |
+
st.error("Error generating questions. Please try again.")
|
| 1242 |
+
except Exception as e:
|
| 1243 |
+
st.error(f"An error occurred: {str(e)}")
|
| 1244 |
+
|
| 1245 |
+
# Display previously generated test if it exists in session state
|
| 1246 |
+
elif hasattr(st.session_state, "generated_questions") and hasattr(
|
| 1247 |
+
st.session_state, "generated_synoptic"
|
| 1248 |
+
):
|
| 1249 |
+
st.subheader("Preview Subjective Questions and Synoptic")
|
| 1250 |
+
for i, (q, s) in enumerate(
|
| 1251 |
+
zip(
|
| 1252 |
+
st.session_state.generated_questions,
|
| 1253 |
+
st.session_state.generated_synoptic,
|
| 1254 |
+
),
|
| 1255 |
+
1,
|
| 1256 |
+
):
|
| 1257 |
+
st.markdown(f"**Question {i}:** {q['question']}")
|
| 1258 |
+
with st.expander(f"View Synoptic {i}"):
|
| 1259 |
+
st.markdown(s)
|
| 1260 |
+
|
| 1261 |
+
if st.button("Pre Save Test"):
|
| 1262 |
+
test_id = pre_save_subjective_test(
|
| 1263 |
+
course_id,
|
| 1264 |
+
session["session_id"],
|
| 1265 |
+
st.session_state.test_title,
|
| 1266 |
+
st.session_state.generated_questions,
|
| 1267 |
+
)
|
| 1268 |
+
if test_id:
|
| 1269 |
+
st.success("Subjective test saved successfully!")
|
| 1270 |
+
# Clear session state after successful save
|
| 1271 |
+
del st.session_state.generated_questions
|
| 1272 |
+
del st.session_state.generated_synoptic
|
| 1273 |
+
del st.session_state.test_title
|
| 1274 |
+
else:
|
| 1275 |
+
st.error("Error saving subjective test.")
|
| 1276 |
+
|
| 1277 |
+
|
| 1278 |
with pre_class_evaluate:
|
| 1279 |
from subjective_test_evaluation import pre_display_evaluation_to_faculty
|
| 1280 |
pre_display_evaluation_to_faculty(session["session_id"], student_id, course_id)
|
| 1281 |
+
|
| 1282 |
+
with pre_class_quiz:
|
| 1283 |
+
if st.session_state.user_type == "faculty":
|
| 1284 |
+
st.subheader("Create Pre-class Quiz")
|
| 1285 |
+
with st.form("create_pre_class_quiz_form"):
|
| 1286 |
+
quiz_title = st.text_input("Quiz Title")
|
| 1287 |
+
num_questions = st.number_input(
|
| 1288 |
+
"Number of Questions",
|
| 1289 |
+
min_value=1,
|
| 1290 |
+
max_value=20,
|
| 1291 |
+
value=5
|
| 1292 |
+
)
|
| 1293 |
+
duration = st.number_input(
|
| 1294 |
+
"Quiz Duration (minutes)",
|
| 1295 |
+
min_value=5,
|
| 1296 |
+
max_value=60,
|
| 1297 |
+
value=15
|
| 1298 |
+
)
|
| 1299 |
|
| 1300 |
+
# Generate quiz button
|
| 1301 |
+
if st.form_submit_button("Generate Pre-class Quiz"):
|
| 1302 |
+
if not quiz_title:
|
| 1303 |
+
st.error("Please enter a quiz title")
|
| 1304 |
+
return
|
| 1305 |
+
|
| 1306 |
+
# Get pre-class materials
|
| 1307 |
+
materials = resources_collection.find({"session_id": session_id})
|
| 1308 |
+
context = ""
|
| 1309 |
+
for material in materials:
|
| 1310 |
+
if 'text_content' in material:
|
| 1311 |
+
context += material['text_content'] + "\n"
|
| 1312 |
+
|
| 1313 |
+
if not context:
|
| 1314 |
+
st.error("No pre-class materials found")
|
| 1315 |
+
return
|
| 1316 |
+
|
| 1317 |
+
# Generate questions
|
| 1318 |
+
questions = generate_mcqs(
|
| 1319 |
+
context=context,
|
| 1320 |
+
num_questions=num_questions,
|
| 1321 |
+
session_title=session['title'],
|
| 1322 |
+
session_description=session.get('description', '')
|
| 1323 |
+
)
|
| 1324 |
+
|
| 1325 |
+
if questions:
|
| 1326 |
+
# Preview questions
|
| 1327 |
+
st.subheader("Preview Questions")
|
| 1328 |
+
for i, q in enumerate(questions, 1):
|
| 1329 |
+
st.markdown(f"**Q{i}:** {q['question']}")
|
| 1330 |
+
for opt in q['options']:
|
| 1331 |
+
st.markdown(f"- {opt}")
|
| 1332 |
+
st.markdown(f"*Correct: {q['correct_option']}*")
|
| 1333 |
+
|
| 1334 |
+
# Save quiz
|
| 1335 |
+
quiz_id = save_pre_class_quiz(
|
| 1336 |
+
course_id=course_id,
|
| 1337 |
+
session_id=session_id,
|
| 1338 |
+
title=quiz_title,
|
| 1339 |
+
questions=questions,
|
| 1340 |
+
user_id=st.session_state.user_id,
|
| 1341 |
+
duration=duration
|
| 1342 |
+
)
|
| 1343 |
+
|
| 1344 |
+
if quiz_id:
|
| 1345 |
+
st.success("Pre-class quiz created successfully!")
|
| 1346 |
+
else:
|
| 1347 |
+
st.error("Error saving quiz")
|
| 1348 |
+
|
| 1349 |
+
|
| 1350 |
+
def display_pre_class_quiz_tab(student_id, course_id, session_id):
|
| 1351 |
+
"""Display pre-class quizzes for students"""
|
| 1352 |
+
st.markdown("### Pre-class Quizzes")
|
| 1353 |
+
|
| 1354 |
+
# Get available quizzes
|
| 1355 |
+
quizzes = quizzes_collection.find({
|
| 1356 |
+
"course_id": course_id,
|
| 1357 |
+
"session_id": session_id,
|
| 1358 |
+
"status": "active"
|
| 1359 |
+
})
|
| 1360 |
+
|
| 1361 |
+
quizzes = list(quizzes)
|
| 1362 |
+
if not quizzes:
|
| 1363 |
+
st.info("No pre-class quizzes available.")
|
| 1364 |
+
return
|
| 1365 |
+
|
| 1366 |
+
for quiz in quizzes:
|
| 1367 |
+
with st.expander(f"📝 {quiz['title']}", expanded=True):
|
| 1368 |
+
# Check if already taken
|
| 1369 |
+
existing_score = get_student_quiz_score(quiz["_id"], student_id)
|
| 1370 |
+
|
| 1371 |
+
if existing_score is not None:
|
| 1372 |
+
st.success(f"Quiz completed! Score: {existing_score:.1f}%")
|
| 1373 |
+
|
| 1374 |
+
# Show review
|
| 1375 |
+
st.subheader("Quiz Review")
|
| 1376 |
+
for i, question in enumerate(quiz["questions"]):
|
| 1377 |
+
st.markdown(f"**Q{i+1}:** {question['question']}")
|
| 1378 |
+
for opt in question["options"]:
|
| 1379 |
+
if opt.startswith(question["correct_option"]):
|
| 1380 |
+
st.markdown(f"✅ {opt}")
|
| 1381 |
+
else:
|
| 1382 |
+
st.markdown(f"- {opt}")
|
| 1383 |
+
|
| 1384 |
+
else:
|
| 1385 |
+
# Check time remaining
|
| 1386 |
+
start_time = quiz.get('start_time')
|
| 1387 |
+
if not start_time:
|
| 1388 |
+
if st.button("Start Quiz", key=f"start_{quiz['_id']}"):
|
| 1389 |
+
quizzes_collection.update_one(
|
| 1390 |
+
{"_id": quiz["_id"]},
|
| 1391 |
+
{"$set": {"start_time": datetime.now()}}
|
| 1392 |
+
)
|
| 1393 |
+
st.rerun()
|
| 1394 |
+
else:
|
| 1395 |
+
time_remaining = (start_time + timedelta(minutes=quiz['duration_minutes'])) - datetime.now()
|
| 1396 |
+
|
| 1397 |
+
if time_remaining.total_seconds() > 0:
|
| 1398 |
+
st.info(f"Time remaining: {int(time_remaining.total_seconds() // 60)}:{int(time_remaining.total_seconds() % 60):02d}")
|
| 1399 |
+
|
| 1400 |
+
# Display quiz form
|
| 1401 |
+
with st.form(f"pre_class_quiz_{quiz['_id']}"):
|
| 1402 |
+
student_answers = {}
|
| 1403 |
+
for i, question in enumerate(quiz["questions"]):
|
| 1404 |
+
st.markdown(f"**Q{i+1}:** {question['question']}")
|
| 1405 |
+
options = [opt for opt in question["options"]]
|
| 1406 |
+
student_answers[str(i)] = st.radio(
|
| 1407 |
+
f"Select answer:",
|
| 1408 |
+
options=options,
|
| 1409 |
+
key=f"q_{quiz['_id']}_{i}"
|
| 1410 |
+
)
|
| 1411 |
+
|
| 1412 |
+
if st.form_submit_button("Submit Quiz"):
|
| 1413 |
+
score = submit_quiz_answers(
|
| 1414 |
+
quiz["_id"],
|
| 1415 |
+
student_id,
|
| 1416 |
+
student_answers
|
| 1417 |
+
)
|
| 1418 |
+
if score is not None:
|
| 1419 |
+
st.success(f"Quiz submitted! Score: {score:.1f}%")
|
| 1420 |
+
st.rerun()
|
| 1421 |
+
else:
|
| 1422 |
+
st.error("Error submitting quiz")
|
| 1423 |
+
else:
|
| 1424 |
+
st.error("Quiz time expired")
|
| 1425 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1426 |
|
| 1427 |
def extract_external_content(url, content_type):
|
| 1428 |
"""Extract content from external resources based on their type"""
|
|
|
|
| 1697 |
if user_type == "faculty":
|
| 1698 |
faculty_id = st.session_state.user_id
|
| 1699 |
|
| 1700 |
+
st.markdown("#### Create Surprise Quiz")
|
| 1701 |
|
| 1702 |
questions = []
|
| 1703 |
with st.form("create_surprise_quiz_form"):
|
|
|
|
| 1712 |
# Option to choose quiz generation method
|
| 1713 |
generation_method = st.radio(
|
| 1714 |
"Question Generation Method",
|
| 1715 |
+
["Generate from Pre-class Materials"],
|
| 1716 |
)
|
| 1717 |
|
| 1718 |
submit_quiz = st.form_submit_button("Generate Surprise Quiz")
|
|
|
|
| 2882 |
|
| 2883 |
# Display student metrics in a grid
|
| 2884 |
st.markdown('<div class="analytics-grid">', unsafe_allow_html=True)
|
| 2885 |
+
|
| 2886 |
+
student_ids = [student["student_id"] for student in analytics["student_analytics"]]
|
| 2887 |
+
student_names = {str(student["_id"]): student["full_name"] for student in students_collection.find({"_id": {"$in": [ObjectId(sid) for sid in student_ids]}})}
|
| 2888 |
+
|
| 2889 |
+
# Display student metrics in a grid
|
| 2890 |
+
st.markdown('<div class="analytics-grid">', unsafe_allow_html=True)
|
| 2891 |
for student in analytics["student_analytics"]:
|
| 2892 |
# Apply filters
|
| 2893 |
if (concept_understanding != "All" and
|
|
|
|
| 2906 |
if struggling_topic != "All" and struggling_topic not in student["struggling_topics"]:
|
| 2907 |
continue
|
| 2908 |
|
| 2909 |
+
student_name = student_names.get(student["student_id"], "Unknown Student")
|
| 2910 |
+
|
| 2911 |
st.markdown(f"""
|
| 2912 |
<div class="student-metrics-card">
|
| 2913 |
<div class="header">
|
| 2914 |
+
<span class="student-id">Student: {student_name}</span>
|
| 2915 |
</div>
|
| 2916 |
<div class="metrics-grid">
|
| 2917 |
<div class="metric-box">
|
|
|
|
| 3683 |
|
| 3684 |
def display_pre_subjective_test_tab(student_id, course_id, session_id):
|
| 3685 |
"""Display subjective tests and results for students"""
|
| 3686 |
+
st.markdown("### Pre-class Subjective Questions ")
|
| 3687 |
|
| 3688 |
try:
|
| 3689 |
print("course_id", course_id, "session_id", session_id, "student_id", student_id)
|
|
|
|
| 3696 |
|
| 3697 |
if not subjective_tests:
|
| 3698 |
print("bruh")
|
| 3699 |
+
st.info("No pre subjective questions available for this session.")
|
| 3700 |
return
|
| 3701 |
|
| 3702 |
# Create tabs for Tests and Results
|