Harshal Vhatkar commited on
Commit
9987aed
·
1 Parent(s): 28db8ae

add new features

Browse files
Files changed (6) hide show
  1. app.py +2 -2
  2. file_upload_vectorize.py +1 -1
  3. gen_mcqs.py +45 -4
  4. inclass_questions.py +738 -0
  5. session_page.py +845 -161
  6. 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
- # from session_page import display_session_content
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-pro')
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
- if materials:
270
- if prompt := st.chat_input("Ask a question about Pre-class Materials"):
271
- # if len(st.session_state.messages) >= 20:
272
- # st.warning("Message limit (20) reached for this session.")
273
- # return
274
-
275
- st.session_state.messages.append({"role": "user", "content": prompt})
276
-
277
- # Display User Message
278
- with st.chat_message("user"):
279
- st.markdown(prompt)
280
-
281
- # Get document context
282
- context = ""
283
- print("Session ID is: ", session['session_id'])
284
- materials = resources_collection.find({"session_id": session['session_id']})
285
- print(materials)
286
- context = ""
287
- vector_data = None
288
-
289
- # for material in materials:
290
- # print(material)
291
- context = ""
292
- for material in materials:
293
- resource_id = material['_id']
294
- print("Supposed Resource ID is: ", resource_id)
295
- vector_data = vectors_collection.find_one({"resource_id": resource_id})
296
- # print(vector_data)
297
- if vector_data and 'text' in vector_data:
298
- context += vector_data['text'] + "\n"
299
-
300
- if not vector_data:
301
- st.error("No Pre-class materials found for this session.")
302
- return
303
-
304
- try:
305
- # Generate response using Gemini
306
- # context_prompt = f"""
307
- # Based on the following context, answer the user's question:
308
 
309
- # Context:
310
- # {context}
311
 
312
- # Question: {prompt}
313
 
314
- # Please provide a clear and concise answer based only on the information provided in the context.
315
- # """
316
- # context_prompt = f"""
317
- # You are a highly intelligent and resourceful assistant capable of synthesizing information from the provided context.
318
-
319
- # Context:
320
- # {context}
321
-
322
- # Instructions:
323
- # 1. Base your answers primarily on the given context.
324
- # 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.
325
- # 3. Only use external knowledge or web assistance when:
326
- # - The context lacks sufficient information, and
327
- # - The question requires knowledge beyond what can be reasonably inferred from the context.
328
- # 4. Clearly state if you are relying on web assistance for any part of your answer.
329
- # 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.
330
-
331
- # Question: {prompt}
332
-
333
- # Please provide a clear and comprehensive answer based on the above instructions.
334
- # """
335
- context_prompt = f"""
336
- You are a highly intelligent and resourceful assistant capable of synthesizing information from the provided context and external sources.
337
-
338
- Context:
339
- {context}
340
-
341
- Instructions:
342
- 1. Base your answers on the provided context wherever possible.
343
- 2. If the answer to the user's question is not explicitly in the context:
344
- - Use external knowledge or web assistance to provide a clear and accurate response.
345
- 3. Do not respond negatively. If the answer is not in the context, use web assistance or your knowledge to generate a thoughtful response.
346
- 4. Clearly state if part of your response relies on web assistance.
347
-
348
- Question: {prompt}
349
-
350
- Please provide a clear and comprehensive answer based on the above instructions.
351
- """
352
-
353
- response = model.generate_content(context_prompt)
354
- if not response or not response.text:
355
- st.error("No response received from the model")
356
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
 
358
- assistant_response = response.text
359
- # Display Assistant Response
360
- with st.chat_message("assistant"):
361
- st.markdown(assistant_response)
 
 
 
 
362
 
363
- # Build the message
364
- new_message = {
365
- "prompt": prompt,
366
- "response": assistant_response,
367
- "timestamp": datetime.utcnow()
368
- }
369
- st.session_state.messages.append(new_message)
 
 
 
 
 
 
 
 
 
 
370
  # Update database
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  try:
372
- chat_history_collection.update_one(
373
- {
374
- "user_id": student_id,
375
- "session_id": session['session_id']
376
- },
377
- {
378
- "$push": {"messages": new_message},
379
- "$setOnInsert": {
380
- "user_id": student_id,
381
- "session_id": session['session_id'],
382
- "timestamp": datetime.utcnow()
383
- }
384
- },
385
- upsert=True
386
- )
387
- except Exception as db_error:
388
- st.error(f"Error saving chat history: {str(db_error)}")
389
- except Exception as e:
390
- st.error(f"Error generating response: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 presentations
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
- # Display pre-class materials
930
- # Group resources by their types
931
- grouped_resources = defaultdict(list)
932
- materials = resources_collection.find({"session_id": session_id})
933
- for material in materials:
934
- grouped_resources[material['material_type']].append(material)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
935
 
936
- # Display grouped resources
937
- for material_type, resources in grouped_resources.items():
938
- st.markdown(f"##### {material_type.capitalize()} Resources")
939
- for material in resources:
940
- resource_info = f"- **{material['file_name']}** ({material['file_type']})"
941
- if 'source_url' in material:
942
- resource_info += f" - [URL]({material['source_url']})"
943
- st.markdown(resource_info)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- try:
1450
- courses_collection.update_one(
1451
- {"course_id": course_id, "sessions.session_id": session['session_id']},
1452
- {"$set": {"sessions.$.session_outline": st.session_state.session_outline}}
1453
- )
1454
- st.success("Outline saved successfully!")
1455
- except Exception as e:
1456
- st.error(f"Error saving outline: {str(e)}")
1457
-
1458
- # Download option
1459
- if st.download_button(
1460
- "📥 Download Outline",
1461
- data=json.dumps(st.session_state.session_outline, indent=2),
1462
- file_name="session_outline.json",
1463
- mime="application/json"
1464
- ):
1465
- st.success("Outline downloaded successfully!")
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
- # courses_collection.update_one(
1533
- # {"course_id": course_id, "sessions.session_id": session['session_id']},
1534
- # {"$set": {"sessions.$.session_outline": outline}}
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 {student["student_id"][-6:]}</span>
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
- start_time = chat_time["start_time"] if chat_time and "start_time" in chat_time else None
294
- print("Start Time is: ", start_time)
295
- if start_time is None:
 
 
 
 
 
 
 
 
 
 
 
296
  if st.button("Start Chat Session (20 minutes)"):
297
- st.session_state.chat_session_start = datetime.now()
 
 
 
298
  chat_time_collection.update_one(
299
- {"session_id": session["session_id"], "user_id": student_id, "course_id": course_id},
300
- {"$set": {"start_time": st.session_state.chat_session_start}},
 
 
 
 
 
 
 
 
 
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
- # Check if chat session is active and within time limit
309
- chat_time = chat_time_collection.find_one(
310
- {"session_id": session["session_id"], "user_id": student_id, "course_id": course_id}
311
- )
312
- cond = False
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
- if not vector_data:
357
- st.error("No Pre-class materials found for this session.")
358
- return
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
- else:
454
- st.warning("Chat session has expired. The 20-minute time limit has been reached.")
455
- st.session_state.chat_session_active = False
456
- st.session_state.chat_session_start = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # display_pre_subjective_test_tab(session, course_id, session_id)
645
- display_pre_subjective_test_tab(
646
- student_id, course_id, session["session_id"]
647
- ) # Added this line
648
- pass
 
 
 
 
 
 
 
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 presentations
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
- # 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
  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.subheader("Create Surprise Quiz")
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", "Generate Random Questions"],
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 {student["student_id"][-6:]}</span>
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.header("Pre Subjective Tests")
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 tests available for this session.")
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