Harshal Vhatkar commited on
Commit
459b8b0
·
1 Parent(s): cea9944

integrate new features

Browse files
Files changed (4) hide show
  1. app.py +1 -1
  2. live_chat_feature.py +530 -0
  3. pre_class_analytics3.py +499 -0
  4. session_page.py +331 -232
app.py CHANGED
@@ -93,7 +93,7 @@ def login_user(username, password, user_type):
93
  # user = students_collection.find_one({"full_name": username}) or students_collection.find_one({"username": username})
94
  user = students_collection.find_one({"$or": [{"full_name": username}, {"username": username}]})
95
  elif user_type == "faculty":
96
- user = faculty_collection.find_one({"full_name": username})
97
  elif user_type == "research_assistant":
98
  user = research_assistants_collection.find_one({"full_name": username})
99
  elif user_type == "analyst":
 
93
  # user = students_collection.find_one({"full_name": username}) or students_collection.find_one({"username": username})
94
  user = students_collection.find_one({"$or": [{"full_name": username}, {"username": username}]})
95
  elif user_type == "faculty":
96
+ user = faculty_collection.find_one({"$or": [{"full_name": username}, {"username": username}]})
97
  elif user_type == "research_assistant":
98
  user = research_assistants_collection.find_one({"full_name": username})
99
  elif user_type == "analyst":
live_chat_feature.py ADDED
@@ -0,0 +1,530 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from pymongo import MongoClient
3
+ from datetime import datetime, timedelta
4
+ from dotenv import load_dotenv
5
+ import os
6
+ import google.generativeai as genai
7
+ from file_upload_vectorize import resources_collection, vectors_collection
8
+ # Load environment variables
9
+ load_dotenv()
10
+ MONGO_URI = os.getenv("MONGO_URI")
11
+ client = MongoClient(MONGO_URI)
12
+ db = client["novascholar_db"]
13
+ live_chat_sessions_collection = db["live_chat_sessions"]
14
+
15
+ # Initialize AI model
16
+ genai.configure(api_key=os.getenv("GEMINI_KEY"))
17
+ model = genai.GenerativeModel("gemini-1.5-flash")
18
+
19
+
20
+ def display_live_chat_interface(session, user_id, course_id):
21
+ """Main interface for live chat sessions - handles both faculty and student views"""
22
+ st.markdown("<div style='margin-top: 20px;'></div>", unsafe_allow_html=True)
23
+ st.subheader("Live Class Chat Session")
24
+
25
+ # Initialize session states
26
+ if 'chat_active' not in st.session_state:
27
+ st.session_state.chat_active = False
28
+ if 'chat_end_time' not in st.session_state:
29
+ st.session_state.chat_end_time = None
30
+ if 'messages' not in st.session_state:
31
+ st.session_state.messages = []
32
+
33
+ # Faculty View
34
+ if st.session_state.user_type == "faculty":
35
+ display_faculty_controls(session, user_id, course_id)
36
+ # Student View
37
+ else:
38
+ display_student_view(session, user_id, course_id)
39
+
40
+ def create_timer_html(end_time):
41
+ """Create a simplified but reliable timer component"""
42
+ end_timestamp = int(end_time.timestamp() * 1000) # Convert to milliseconds
43
+ current_timestamp = int(datetime.utcnow().timestamp() * 1000)
44
+
45
+ return f"""
46
+ <div id="timer-container">
47
+ <style>
48
+ .timer-box {{
49
+ background: #f0f2f6;
50
+ border-radius: 8px;
51
+ padding: 15px;
52
+ text-align: center;
53
+ margin: 10px 0;
54
+ }}
55
+ .timer-display {{
56
+ font-size: 24px;
57
+ font-weight: bold;
58
+ color: #0066cc;
59
+ margin-bottom: 10px;
60
+ }}
61
+ .progress-bar {{
62
+ width: 100%;
63
+ height: 8px;
64
+ background: #e0e0e0;
65
+ border-radius: 4px;
66
+ overflow: hidden;
67
+ }}
68
+ .progress {{
69
+ height: 100%;
70
+ background: #00aa00;
71
+ transition: width 1s linear;
72
+ }}
73
+ </style>
74
+
75
+ <div class="timer-box">
76
+ <div id="timer" class="timer-display">Calculating...</div>
77
+ <div class="progress-bar">
78
+ <div id="progress" class="progress"></div>
79
+ </div>
80
+ </div>
81
+
82
+ <script>
83
+ function updateTimer() {{
84
+ const endTime = {end_timestamp};
85
+ const startTime = {current_timestamp};
86
+ const now = new Date().getTime();
87
+ const totalDuration = endTime - startTime;
88
+ const timeLeft = endTime - now;
89
+
90
+ const timer = document.getElementById('timer');
91
+ const progress = document.getElementById('progress');
92
+
93
+ if (timeLeft <= 0) {{
94
+ timer.innerHTML = 'Session Ended';
95
+ timer.style.color = '#ff4444';
96
+ progress.style.width = '100%';
97
+ progress.style.background = '#ff4444';
98
+ return;
99
+ }}
100
+
101
+ const minutes = Math.floor(timeLeft / (1000 * 60));
102
+ const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);
103
+ const progressWidth = ((totalDuration - timeLeft) / totalDuration) * 100;
104
+
105
+ timer.innerHTML = `${{minutes}}:${{seconds < 10 ? '0' : ''}}${{seconds}}`;
106
+ progress.style.width = `${{progressWidth}}%`;
107
+
108
+ if (minutes < 5) {{
109
+ progress.style.background = '#ffa500';
110
+ }}
111
+ }}
112
+
113
+ const timerInterval = setInterval(updateTimer, 1000);
114
+ updateTimer();
115
+ </script>
116
+ </div>
117
+ """
118
+
119
+ # def display_timer(end_time):
120
+ # """Display a simple countdown timer"""
121
+ # if not isinstance(end_time, datetime):
122
+ # return
123
+
124
+ # # Calculate remaining time
125
+ # remaining_time = end_time - datetime.utcnow()
126
+
127
+ # if remaining_time.total_seconds() <= 0:
128
+ # st.session_state.chat_active = False
129
+ # st.session_state.chat_end_time = None
130
+ # return
131
+
132
+ # # Convert to minutes and seconds
133
+ # total_seconds = int(remaining_time.total_seconds())
134
+ # minutes = total_seconds // 60
135
+ # seconds = total_seconds % 60
136
+
137
+ # # Create a progress value between 0 and 100
138
+ # original_duration = st.session_state.get('original_duration', 15) * 60 # default 15 minutes in seconds
139
+ # progress = ((original_duration - total_seconds) / original_duration)
140
+
141
+ # # Display timer using columns for better layout
142
+ # col1, col2 = st.columns([1, 28])
143
+
144
+ # with col1:
145
+ # st.markdown("### ⏱️")
146
+ # with col2:
147
+ # # Display time remaining
148
+ # st.markdown(f"### {minutes:02d}:{seconds:02d}")
149
+
150
+ # # Display progress bar
151
+ # if minutes < 5:
152
+ # color = "orange"
153
+ # else:
154
+ # color = "blue"
155
+
156
+ # st.progress(progress, text="Session Progress")
157
+
158
+ def display_timer(end_time):
159
+ """Display a simple countdown timer"""
160
+ if not isinstance(end_time, datetime):
161
+ return
162
+
163
+ # Calculate remaining time
164
+ remaining_time = end_time - datetime.utcnow()
165
+
166
+ if remaining_time.total_seconds() <= 0:
167
+ st.session_state.chat_active = False
168
+ st.session_state.chat_end_time = None
169
+ return
170
+
171
+ # Convert to minutes and seconds
172
+ total_seconds = int(remaining_time.total_seconds())
173
+ minutes = total_seconds // 60
174
+ seconds = total_seconds % 60
175
+
176
+ # Create a progress value between 0 and 100
177
+ original_duration = st.session_state.get('original_duration', 15) * 60 # default 15 minutes in seconds
178
+ progress = ((original_duration - total_seconds) / original_duration)
179
+
180
+ # Custom CSS for timer layout
181
+ st.markdown("""
182
+ <style>
183
+ .timer-container {{
184
+ margin: 1rem 0;
185
+ }}
186
+ .timer-row {{
187
+ display: flex;
188
+ justify-content: space-between;
189
+ align-items: center;
190
+ gap: 1rem;
191
+ margin-bottom: 0.15rem;
192
+ }}
193
+ .timer-icon {{
194
+ font-size: 1.5rem;
195
+ }}
196
+ .timer-text {{
197
+ font-size: 1.2rem;
198
+ font-weight: bold;
199
+ }}
200
+ .timer-progress-label {{
201
+ font-size: 1.1rem;
202
+ font-weight: semi-bold;
203
+ }}
204
+ .stProgress {{
205
+ margin-bottom: 0.25rem;
206
+ }}
207
+ .stChatInputContainer {{
208
+ margin-top: 1rem;
209
+ }}
210
+ </style>
211
+
212
+ <div class="timer-container">
213
+ <div class="timer-row">
214
+ <div class="progress-text"><span class="timer-progress-label">Session Progress</span></div>
215
+ <div class="timer-progress">
216
+ <span class="timer-icon">⏱️</span>
217
+ <span class="timer-text">{:02d}:{:02d}</span>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ """.format(minutes, seconds), unsafe_allow_html=True)
222
+
223
+ # Display progress bar
224
+ color = "orange" if minutes < 5 else "blue"
225
+ st.progress(progress)
226
+
227
+ def display_faculty_controls(session, faculty_id, course_id):
228
+ """Display faculty controls for managing chat sessions"""
229
+
230
+ # Show scheduled sessions
231
+ st.markdown("<div style='margin-top: 20px;'></div>", unsafe_allow_html=True)
232
+ st.markdown("##### 📅 Scheduled Chat Sessions")
233
+ scheduled_sessions = list(live_chat_sessions_collection.find({
234
+ "session_id": session['session_id'],
235
+ "status": "scheduled"
236
+ }))
237
+
238
+ # Schedule new session
239
+ with st.expander("➕ Schedule New Chat Session"):
240
+ col1, col2, col3 = st.columns([2, 2, 1])
241
+ with col1:
242
+ session_date = st.date_input("📅 Date", min_value=datetime.now().date())
243
+ with col2:
244
+ session_time = st.time_input("🕒 Time", value=datetime.now().time())
245
+ with col3:
246
+ duration = st.selectbox(
247
+ "⏱️ Duration (mins)",
248
+ options=[5, 10, 15, 20, 30, 45, 60],
249
+ index=2
250
+ )
251
+
252
+ if st.button("📋 Schedule Session"):
253
+ session_datetime = datetime.combine(session_date, session_time)
254
+ if session_datetime < datetime.now():
255
+ st.error("❌ Cannot schedule sessions in the past!")
256
+ else:
257
+ live_chat_sessions_collection.insert_one({
258
+ "session_id": session['session_id'],
259
+ "course_id": course_id,
260
+ "faculty_id": faculty_id,
261
+ "start_time": session_datetime,
262
+ "duration": duration,
263
+ "status": "scheduled",
264
+ "chats": []
265
+ })
266
+ st.success("✅ Chat session scheduled successfully!")
267
+ st.rerun()
268
+
269
+
270
+
271
+ if scheduled_sessions:
272
+ for scheduled in scheduled_sessions:
273
+ with st.expander(f"📌 Scheduled: {scheduled['start_time'].strftime('%I:%M %p')}"):
274
+ st.write(f"⏱️ Duration: {scheduled['duration']} minutes")
275
+ if st.button("❌ Cancel Session", key=f"cancel_{scheduled['_id']}", type="secondary"):
276
+ live_chat_sessions_collection.delete_one({"_id": scheduled['_id']})
277
+ st.rerun()
278
+
279
+ # Start immediate session
280
+ if not st.session_state.get('chat_active', False):
281
+ st.markdown("<div style='margin-top: 20px;'></div>", unsafe_allow_html=True)
282
+ st.markdown("##### 🎯 Start Immediate Session")
283
+ col1, col2 = st.columns([3, 1])
284
+ with col1:
285
+ immediate_duration = st.selectbox(
286
+ "⏱️ Select duration (minutes)",
287
+ options=[5, 10, 15, 20, 30, 45, 60],
288
+ index=2
289
+ )
290
+ with col2:
291
+ st.markdown("<div style='margin-top: 26px;'>", unsafe_allow_html=True)
292
+ if st.button("▶️ Start", use_container_width=True):
293
+ st.session_state.chat_active = True
294
+ st.session_state.chat_end_time = datetime.utcnow() + timedelta(minutes=immediate_duration)
295
+ st.session_state.original_duration = immediate_duration # Store original duration
296
+ live_chat_sessions_collection.insert_one({
297
+ "session_id": session['session_id'],
298
+ "course_id": course_id,
299
+ "faculty_id": faculty_id,
300
+ "start_time": datetime.utcnow(),
301
+ "duration": immediate_duration,
302
+ "status": "active",
303
+ "chats": []
304
+ })
305
+ st.rerun()
306
+ st.markdown("</div>", unsafe_allow_html=True)
307
+ else:
308
+ # Show timer for active session
309
+ if hasattr(st.session_state, 'chat_end_time'):
310
+ display_timer(st.session_state.chat_end_time)
311
+
312
+ if st.button("⏹️ End Session", type="secondary"):
313
+ st.session_state.chat_active = False
314
+ st.session_state.chat_end_time = None
315
+ live_chat_sessions_collection.update_one(
316
+ {"session_id": session['session_id'], "status": "active"},
317
+ {"$set": {"status": "completed", "end_time": datetime.utcnow()}}
318
+ )
319
+ st.rerun()
320
+
321
+ def get_session_context(session, session_id, course_id):
322
+ """Retrieve session context for AI model"""
323
+ # Get session data
324
+ session = live_chat_sessions_collection.find_one({"session_id": session_id})
325
+ if not session:
326
+ st.error("Session not found")
327
+ return
328
+
329
+ # Get pre-class materials
330
+ context = ""
331
+ materials = resources_collection.find({"session_id": session_id})
332
+ for material in materials:
333
+ resource_id = material['_id']
334
+ vector_data = vectors_collection.find_one({"resource_id": resource_id})
335
+ if vector_data and 'text' in vector_data:
336
+ context += vector_data['text'] + "\n"
337
+
338
+ courses_collection = db["courses"]
339
+ course = courses_collection.find_one({"_id": course_id})
340
+ session = courses_collection.find_one({"sessions.session_id": session_id})
341
+ if course:
342
+ context += f"Course: {course['course_name']}\n"
343
+ if session:
344
+ context += f"Session: {session['title']}\n"
345
+ if 'session_learning_outcomes' in session:
346
+ context += f"Session Learning Outcomes: {', '.join(session['session_learning_outcomes'])}\n"
347
+ return context
348
+
349
+ def display_student_view(session, student_id, course_id):
350
+ """Display student interface for chat sessions"""
351
+
352
+ # Show upcoming scheduled sessions
353
+ st.markdown("<div style='margin-top: 20px;'></div>", unsafe_allow_html=True)
354
+ st.markdown("##### 📅 Upcoming Chat Sessions")
355
+ upcoming_sessions = list(live_chat_sessions_collection.find({
356
+ "session_id": session['session_id'],
357
+ "status": "scheduled",
358
+ "start_time": {"$gt": datetime.utcnow()}
359
+ }).sort("start_time", 1))
360
+
361
+ if upcoming_sessions:
362
+ for upcoming in upcoming_sessions:
363
+ with st.expander(f"📌 {upcoming['start_time'].strftime('%I:%M %p')}"):
364
+ st.write(f"⏱️ Duration: {upcoming['duration']} minutes")
365
+ time_until = upcoming['start_time'] - datetime.utcnow()
366
+ st.write(f"🕒 Starts in: {time_until.seconds // 3600}h {(time_until.seconds % 3600) // 60}m")
367
+ else:
368
+ st.info("📝 No upcoming chat sessions scheduled")
369
+
370
+ # Check for active session
371
+ active_session = live_chat_sessions_collection.find_one({
372
+ "session_id": session['session_id'],
373
+ "status": "active"
374
+ })
375
+
376
+ # if active_session:
377
+ # st.session_state.chat_active = True
378
+ # st.session_state.chat_end_time = active_session['start_time'] + timedelta(minutes=active_session['duration'])
379
+ if active_session and not st.session_state.get('chat_active', False):
380
+ st.session_state.chat_active = True
381
+ st.session_state.chat_end_time = active_session['start_time'] + timedelta(minutes=active_session['duration'])
382
+ st.session_state.original_duration = active_session['duration']
383
+
384
+ if st.session_state.get('chat_active', False):
385
+ st.markdown("<div style='margin-top: 20px;'></div>", unsafe_allow_html=True)
386
+ st.markdown("##### 🔴 Live Chat Section")
387
+
388
+ if hasattr(st.session_state, 'chat_end_time'):
389
+ reamining_time = st.session_state.chat_end_time - datetime.utcnow()
390
+ if reamining_time.total_seconds() > 0:
391
+ display_timer(st.session_state.chat_end_time)
392
+ active_session = live_chat_sessions_collection.find_one({
393
+ "session_id": session['session_id'],
394
+ "status": "active"
395
+ })
396
+ if active_session:
397
+ faculty_id = active_session['faculty_id']
398
+ display_chat_interface(session, student_id, course_id, faculty_id)
399
+ else:
400
+ st.error("❌ No active session found.")
401
+ st.session_state.chat_active = False
402
+ st.session_state.chat_end_time = None
403
+ else:
404
+ st.session_state.chat_active = False
405
+ st.session_state.chat_end_time = None
406
+ live_chat_sessions_collection.update_one(
407
+ {"session_id": session['session_id'], "status": "active"},
408
+ {"$set": {"status": "completed", "end_time": datetime.utcnow()}}
409
+ )
410
+ st.rerun()
411
+
412
+ def display_chat_interface(session, user_id, course_id, faculty_id):
413
+ """Display the actual chat interface with messages"""
414
+ # Display chat messages
415
+ # Initialize 'messages' in session_state if it doesn't exist
416
+ if 'messages' not in st.session_state:
417
+ st.session_state.messages = []
418
+
419
+ # Chat input
420
+ if prompt := st.chat_input("Ask Questions about the Session"):
421
+ # Get session context
422
+ context = ""
423
+ context = get_session_context(session, session['session_id'], course_id)
424
+
425
+ st.session_state.messages.append({"role": "user", "content": prompt})
426
+
427
+ # Display user message
428
+ with st.chat_message("user"):
429
+ st.markdown(prompt)
430
+
431
+ try:
432
+ # Generate AI response
433
+ context_prompt = f"""
434
+ You are an intelligent teaching assistant participating in a live class discussion.
435
+
436
+ Session Context:
437
+ {context}
438
+
439
+ Current Question by the Student: {prompt}
440
+
441
+ Instructions:
442
+ 1. Provide clear, concise responses that relate to the session's key concepts and learning outcomes
443
+ 2. Encourage critical thinking and discussion
444
+ 3. Keep responses focused and relevant to the current topic
445
+ 4. If students seem confused, provide clarifying examples
446
+
447
+ Please provide an appropriate response for this live classroom discussion.
448
+ """
449
+
450
+ response = model.generate_content(context_prompt)
451
+ assistant_response = response.text
452
+
453
+ # Display assistant response
454
+ with st.chat_message("assistant"):
455
+ st.markdown(assistant_response)
456
+
457
+ # Save chat message
458
+ chat_message = {
459
+ "session_id": session['session_id'],
460
+ "timestamp": datetime.utcnow(),
461
+ "user_id": user_id,
462
+ "user_type": st.session_state.user_type,
463
+ "message": prompt,
464
+ "response": assistant_response
465
+ }
466
+ st.session_state.messages.append(chat_message)
467
+ # chat_messages_collection.insert_one(chat_message)
468
+ # Update database
469
+ try:
470
+ current_session = live_chat_sessions_collection.find_one({"session_id": session['session_id'], "status": "active"})
471
+ live_chat_sessions_collection.update_one(
472
+ {
473
+ "session_id": session['session_id'],
474
+ "course_id": course_id,
475
+ "faculty_id": faculty_id,
476
+ "start_time": current_session['start_time'],
477
+ "duration": current_session['duration'],
478
+ "status": current_session['status'],
479
+ "chats.user_id": user_id
480
+ },
481
+ {
482
+ "$push": {
483
+ "chats.$.messages": {
484
+ "prompt": prompt,
485
+ "response": assistant_response,
486
+ "timestamp": datetime.utcnow()
487
+ }
488
+ }
489
+ }
490
+ )
491
+
492
+ # If no existing chat object for the user, create a new one
493
+ if live_chat_sessions_collection.find_one({
494
+ "session_id": session['session_id'],
495
+ "course_id": course_id,
496
+ "faculty_id": faculty_id,
497
+ "start_time": current_session['start_time'],
498
+ "duration": current_session['duration'],
499
+ "status": current_session['status'],
500
+ "chats.user_id": user_id
501
+ }) is None:
502
+ live_chat_sessions_collection.update_one(
503
+ {
504
+ "session_id": session['session_id'],
505
+ "course_id": course_id,
506
+ "faculty_id": faculty_id,
507
+ "start_time": current_session['start_time'],
508
+ "duration": current_session['duration'],
509
+ "status": current_session['status']
510
+ },
511
+ {
512
+ "$push": {
513
+ "chats": {
514
+ "user_id": user_id,
515
+ "messages": [
516
+ {
517
+ "prompt": prompt,
518
+ "response": assistant_response,
519
+ "timestamp": datetime.utcnow()
520
+ }
521
+ ]
522
+ }
523
+ }
524
+ },
525
+ upsert=True
526
+ )
527
+ except Exception as db_error:
528
+ st.error(f"Error saving chat history: {str(db_error)}")
529
+ except Exception as e:
530
+ st.error(f"Error processing message: {str(e)}")
pre_class_analytics3.py ADDED
@@ -0,0 +1,499 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ import json
3
+ from bson import ObjectId
4
+ import typing_extensions as typing
5
+ import google.generativeai as genai
6
+ from typing import List, Dict, Any
7
+ import numpy as np
8
+ from collections import defaultdict
9
+
10
+ from dotenv import load_dotenv
11
+ import os
12
+ import pymongo
13
+ from pymongo import MongoClient
14
+
15
+ load_dotenv()
16
+ GEMINI_API_KEY = os.getenv("GEMINI_KEY")
17
+
18
+ class NovaScholarAnalytics:
19
+ def __init__(self, model_name: str = "gemini-1.5-flash"):
20
+ genai.configure(api_key=GEMINI_API_KEY)
21
+ self.model = genai.GenerativeModel(model_name)
22
+
23
+ def _preprocess_chat_histories(self, chat_histories: List[Dict]) -> List[Dict]:
24
+ # Code 2:
25
+ """Preprocess chat histories to focus on relevant information."""
26
+ processed = []
27
+
28
+ for chat in chat_histories:
29
+ # Convert ObjectId to string if it's an ObjectId
30
+ user_id = str(chat["user_id"]["$oid"]) if isinstance(chat["user_id"], dict) and "$oid" in chat["user_id"] else str(chat["user_id"])
31
+
32
+ try:
33
+ processed_chat = {
34
+ "user_id": user_id,
35
+ "messages": [
36
+ {
37
+ "prompt": msg["prompt"],
38
+ "response": msg["response"]
39
+ }
40
+ for msg in chat["messages"]
41
+ ]
42
+ }
43
+ processed.append(processed_chat)
44
+ print(f"Successfully processed chat for user: {user_id}")
45
+ except Exception as e:
46
+ print(f"Error processing chat for user: {user_id}")
47
+ print(f"Error details: {str(e)}")
48
+ continue
49
+
50
+ return processed
51
+
52
+ def _create_analytics_prompt(self, chat_histories: List[Dict], all_topics: List[str]) -> str:
53
+ """Creates a structured prompt for Gemini to analyze chat histories."""
54
+ return f"""Analyze the provided student chat histories for a university course and generate concise, actionable analytics WITH EVIDENCE.
55
+
56
+ Context:
57
+ - Chat histories: {json.dumps(chat_histories, indent=2)}
58
+ - These are pre-class interactions between students and an AI tutor
59
+ - Topics covered: {', '.join(all_topics)}
60
+
61
+ Your task is to provide analytics with supporting evidence from the chat histories.
62
+
63
+ Output Format (strictly follow this JSON structure):
64
+ {{
65
+ "topic_wise_insights": [
66
+ {{
67
+ "topic": "<string>",
68
+ "struggling_percentage": <number between 0 and 1>,
69
+ "evidence": {{
70
+ "calculation": "Explain how struggling_percentage was calculated",
71
+ "supporting_messages": [
72
+ {{
73
+ "user_id": "<string>",
74
+ "message": "<string>",
75
+ "reasoning": "Why this message indicates struggling"
76
+ }}
77
+ ]
78
+ }},
79
+ "key_issues": ["<string>"],
80
+ "key_misconceptions": ["<string>"],
81
+ "evidence_for_issues": [
82
+ {{
83
+ "issue": "<string>",
84
+ "supporting_messages": [
85
+ {{
86
+ "user_id": "<string>",
87
+ "message": "<string>"
88
+ }}
89
+ ]
90
+ }}
91
+ ]
92
+ }}
93
+ ],
94
+ "ai_recommended_actions": [
95
+ {{
96
+ "action": "<string>",
97
+ "priority": "high|medium|low",
98
+ "reasoning": "<string>",
99
+ "evidence": {{
100
+ "supporting_messages": [
101
+ {{
102
+ "user_id": "<string>",
103
+ "message": "<string>",
104
+ "relevance": "Why this message supports the recommendation"
105
+ }}
106
+ ],
107
+ "pattern_description": "Description of the pattern observed in chat histories"
108
+ }},
109
+ "expected_outcome": "<string>"
110
+ }}
111
+ ],
112
+ "student_analytics": [
113
+ {{
114
+ "student_id": "<string>",
115
+ "engagement_metrics": {{
116
+ "participation_level": <number between 0 and 1>,
117
+ "concept_understanding": "strong|moderate|needs_improvement",
118
+ "question_quality": "advanced|intermediate|basic"
119
+ }},
120
+ "evidence": {{
121
+ "participation_calculation": "Explain how participation_level was calculated",
122
+ "understanding_evidence": [
123
+ {{
124
+ "message": "<string>",
125
+ "analysis": "Why this indicates their understanding level"
126
+ }}
127
+ ],
128
+ "question_quality_evidence": [
129
+ {{
130
+ "question": "<string>",
131
+ "analysis": "Why this question is classified at this level"
132
+ }}
133
+ ]
134
+ }},
135
+ "struggling_topics": ["<string>"],
136
+ "personalized_recommendation": "<string>"
137
+ }}
138
+ ]
139
+ }}
140
+
141
+ Guidelines for Analysis:
142
+ 1. For every insight, recommendation, or metric, provide specific evidence from the chat histories
143
+ 2. Explain calculations (e.g., how struggling_percentage was derived)
144
+ 3. Include relevant message excerpts that support each conclusion
145
+ 4. For recommendations, show the pattern of student interactions that led to that recommendation
146
+ 5. When analyzing question quality or understanding, provide reasoning for the classification
147
+
148
+ The response must adhere strictly to the above JSON structure, with all fields populated appropriately."""
149
+
150
+ def _validate_analytics_with_evidence(self, initial_analytics: Dict) -> Dict:
151
+ """Validate the initial analytics by checking evidence."""
152
+ validation_prompt = f"""Review and validate the following analytics based on the provided evidence.
153
+
154
+ Analytics to validate: {json.dumps(initial_analytics, indent=2)}
155
+
156
+ For each section:
157
+ 1. Verify if the evidence supports the conclusions
158
+ 2. Check if calculations (percentages, metrics) are justified by the data
159
+ 3. Validate if recommendations are supported by patterns in the chat history
160
+
161
+ Return a JSON with the same structure, but only include insights/recommendations that have strong supporting evidence.
162
+ For any removed items, include them in a separate "insufficient_evidence" section with explanation."""
163
+
164
+ try:
165
+ validation_response = self.model.generate_content(
166
+ validation_prompt,
167
+ generation_config=genai.GenerationConfig(
168
+ response_mime_type="application/json",
169
+ temperature=0.1
170
+ )
171
+ )
172
+
173
+ validated_analytics = json.loads(validation_response.text)
174
+ return validated_analytics
175
+
176
+ except Exception as e:
177
+ print(f"Error in validation: {str(e)}")
178
+ return initial_analytics
179
+
180
+ def _enrich_analytics(self, analytics: Dict) -> Dict:
181
+ """Add derived insights and metrics to the validated analytics."""
182
+ try:
183
+ # Calculate class distribution
184
+ total_students = len(analytics.get("student_insights", []))
185
+ performance_distribution = defaultdict(int)
186
+
187
+ for student in analytics.get("student_insights", []):
188
+ metrics = student.get("engagement_metrics", {})
189
+ understanding = metrics.get("concept_understanding", "moderate")
190
+
191
+ if understanding == "strong":
192
+ performance_distribution["high_performers"] += 1
193
+ elif understanding == "needs_improvement":
194
+ performance_distribution["at_risk"] += 1
195
+ else:
196
+ performance_distribution["average_performers"] += 1
197
+
198
+ # Convert to percentages
199
+ class_distribution = {
200
+ level: count/total_students if total_students > 0 else 0
201
+ for level, count in performance_distribution.items()
202
+ }
203
+
204
+ # Calculate overall engagement
205
+ engagement_sum = sum(
206
+ student.get("engagement_metrics", {}).get("participation_level", 0)
207
+ for student in analytics.get("student_insights", [])
208
+ )
209
+ overall_engagement = engagement_sum / total_students if total_students > 0 else 0
210
+
211
+ # Identify critical topics (those with high struggling percentage)
212
+ critical_topics = [
213
+ topic["topic"]
214
+ for topic in analytics.get("topic_wise_insights", [])
215
+ if topic.get("struggling_percentage", 0) > 0.7 # 70% threshold
216
+ ]
217
+
218
+ # Identify students needing intervention
219
+ immediate_attention = []
220
+ monitoring_required = []
221
+
222
+ for student in analytics.get("student_insights", []):
223
+ student_id = student.get("student_id")
224
+ metrics = student.get("engagement_metrics", {})
225
+
226
+ # Check for immediate attention needed
227
+ if (metrics.get("concept_understanding") == "needs_improvement" or
228
+ metrics.get("participation_level", 0) < 0.3 or # Less than 30% participation
229
+ len(student.get("struggling_topics", [])) > 2): # Struggling with more than 2 topics
230
+ immediate_attention.append(student_id)
231
+ # Check for monitoring
232
+ elif (metrics.get("concept_understanding") == "moderate" or
233
+ metrics.get("participation_level", 0) < 0.5): # Less than 50% participation
234
+ monitoring_required.append(student_id)
235
+
236
+ # Add enriched data to analytics
237
+ analytics["course_health"] = {
238
+ "overall_engagement": overall_engagement,
239
+ "critical_topics": critical_topics,
240
+ "class_distribution": class_distribution
241
+ }
242
+
243
+ analytics["intervention_metrics"] = {
244
+ "immediate_attention_needed": immediate_attention,
245
+ "monitoring_required": monitoring_required
246
+ }
247
+
248
+ # Add evidence for enriched metrics
249
+ analytics["course_health"]["evidence"] = {
250
+ "engagement_calculation": f"Calculated from average participation level of {total_students} students",
251
+ "critical_topics_criteria": "Topics where over 70% of students are struggling",
252
+ "distribution_calculation": "Based on concept understanding levels from student metrics"
253
+ }
254
+
255
+ analytics["intervention_metrics"]["evidence"] = {
256
+ "immediate_attention_criteria": "Students with low understanding, participation < 30%, or >2 struggling topics",
257
+ "monitoring_criteria": "Students with moderate understanding or participation < 50%"
258
+ }
259
+
260
+ return analytics
261
+
262
+ except Exception as e:
263
+ print(f"Error enriching analytics: {str(e)}")
264
+ return analytics # Return original analytics if enrichment fails
265
+
266
+ def generate_analytics(self, chat_histories: List[Dict], all_topics: List[str]) -> Dict:
267
+ """Main method to generate analytics with evidence-based validation."""
268
+ try:
269
+ if not chat_histories or not all_topics:
270
+ print("Missing required input data")
271
+ return self._fallback_analytics()
272
+
273
+ try:
274
+ processed_histories = self._preprocess_chat_histories(chat_histories)
275
+ print("Successfully preprocessed chat histories")
276
+ except Exception as preprocess_error:
277
+ print(f"Error in preprocessing: {str(preprocess_error)}")
278
+ return self._fallback_analytics()
279
+
280
+ try:
281
+ prompt = self._create_analytics_prompt(processed_histories, all_topics)
282
+ print("Successfully created prompt")
283
+ print("Prompt preview:", prompt[:200] + "...") # Print first 200 chars
284
+ except Exception as prompt_error:
285
+ print(f"Error in prompt creation: {str(prompt_error)}")
286
+ return self._fallback_analytics()
287
+
288
+ # Generate initial analytics with evidence
289
+ # prompt = self._create_analytics_prompt(chat_histories, all_topics)
290
+ response = self.model.generate_content(
291
+ prompt,
292
+ generation_config=genai.GenerationConfig(
293
+ response_mime_type="application/json",
294
+ temperature=0.15
295
+ )
296
+ )
297
+ print(response.text)
298
+
299
+ if not response.text:
300
+ print("Empty response from Gemini")
301
+ return self._fallback_analytics()
302
+
303
+ # Parse initial analytics
304
+ # initial_analytics = self._process_gemini_response(response.text)
305
+ initial_analytics2 = json.loads(response.text)
306
+ print("Initial analytics:", initial_analytics2)
307
+ # print("Initial analytics type:", type(initial_analytics2))
308
+ # print("Moving to validation...")
309
+
310
+ # Validate analytics using evidence
311
+ validated_analytics = self._validate_analytics_with_evidence(initial_analytics2)
312
+
313
+ # # Enrich with additional metrics
314
+ final_analytics = self._enrich_analytics(validated_analytics)
315
+
316
+ return final_analytics
317
+
318
+ except Exception as e:
319
+ print(f"Error generating analytics: {str(e)}")
320
+ return self._fallback_analytics()
321
+
322
+ def _fallback_analytics(self) -> Dict:
323
+ """Provide fallback analytics with explanation."""
324
+ return {
325
+ "topic_insights": [],
326
+ "student_insights": [],
327
+ "recommended_actions": [
328
+ {
329
+ "action": "Review analytics generation process",
330
+ "priority": "high",
331
+ "target_group": "system_administrators",
332
+ "reasoning": "Analytics generation failed",
333
+ "expected_impact": "Restore analytics functionality",
334
+ "evidence": {
335
+ "error": "Analytics generation failed to complete"
336
+ }
337
+ }
338
+ ],
339
+ "course_health": {
340
+ "overall_engagement": 0,
341
+ "critical_topics": [],
342
+ "class_distribution": {
343
+ "high_performers": 0,
344
+ "average_performers": 0,
345
+ "at_risk": 0
346
+ }
347
+ },
348
+ "intervention_metrics": {
349
+ "immediate_attention_needed": [],
350
+ "monitoring_required": []
351
+ }
352
+ }
353
+ def _process_gemini_response(self, response: str) -> Dict:
354
+ print("Entered here")
355
+ try:
356
+ analytics = json.loads(response, object_hook=json_serializer)
357
+ if not isinstance(analytics, dict):
358
+ raise ValueError("Invalid response format")
359
+ return analytics
360
+ except Exception as e:
361
+ print(f"Error processing Gemini response: {str(e)}")
362
+ return self._fallback_analytics()
363
+
364
+ load_dotenv()
365
+ MONGODB_URI = os.getenv("MONGO_URI")
366
+ from file_upload_vectorize import model
367
+ import streamlit as st
368
+
369
+ def extract_topics_from_materials(session_id):
370
+ """Extract topics from pre-class materials"""
371
+ materials = resources_collection.find({"session_id": session_id})
372
+ texts = ""
373
+ if materials:
374
+ for material in materials:
375
+ if 'text_content' in material:
376
+ text = material['text_content']
377
+ texts += text + "\n"
378
+ else:
379
+ st.warning("No text content found in the material.")
380
+ return
381
+ else:
382
+ st.error("No pre-class materials found for this session.")
383
+ return
384
+
385
+ if texts:
386
+ context_prompt = f"""
387
+ Task: Extract Comprehensive Topics in a List Format
388
+ You are tasked with analyzing the provided text content and extracting a detailed, flat list of topics.
389
+
390
+ Instructions:
391
+ Identify All Topics: Extract a comprehensive list of all topics, subtopics, and indirect topics present in the provided text content. This list should include:
392
+
393
+ Overarching themes
394
+ Main topics
395
+ Subtopics and their sub-subtopics
396
+ Indirectly related topics
397
+ Flat List Format: Provide a flat list where each item is a topic. Ensure topics at all levels (overarching, main, sub, sub-sub, indirect) are represented as individual entries in the list.
398
+
399
+ Be Exhaustive: Ensure the response captures every topic, subtopic, and indirectly related concept comprehensively.
400
+
401
+ Output Requirements:
402
+ Use this structure:
403
+ {{
404
+ "topics": [
405
+ "Topic 1",
406
+ "Topic 2",
407
+ "Topic 3",
408
+ ...
409
+ ]
410
+ }}
411
+ Do Not Include: Do not include backticks, hierarchical structures, or the word 'json' in your response.
412
+
413
+ Content to Analyze:
414
+ {texts}
415
+ """
416
+ try:
417
+ # response = model.generate_content(context_prompt, generation_config=genai.GenerationConfig(response_mime_type="application/json", response_schema=list[Topics]))
418
+ response = model.generate_content(context_prompt, generation_config=genai.GenerationConfig(temperature=0.3))
419
+ if not response or not response.text:
420
+ st.error("Error extracting topics from materials.")
421
+ return
422
+
423
+ topics = response.text
424
+ return topics
425
+ except Exception as e:
426
+ st.error(f"Error extracting topics: {str(e)}")
427
+ return None
428
+ else:
429
+ st.error("No text content found in the pre-class materials.")
430
+ return None
431
+
432
+
433
+ def get_chat_history(user_id, session_id):
434
+ query = {
435
+ "user_id": ObjectId(user_id),
436
+ "session_id": session_id,
437
+ "timestamp": {"$lte": datetime.utcnow()}
438
+ }
439
+ result = chat_history_collection.find(query)
440
+ return list(result)
441
+
442
+ def json_serializer(obj):
443
+ if isinstance(obj, ObjectId):
444
+ return str(obj)
445
+ raise TypeError(f"Type {type(obj)} not serializable")
446
+
447
+ if __name__ == "__main__":
448
+ client = MongoClient(MONGODB_URI)
449
+ db = client["novascholar_db"]
450
+ chat_history_collection = db["chat_history"]
451
+ resources_collection = db["resources"]
452
+ session_id = "S104"
453
+ # Connect to MongoDB
454
+ user_ids = chat_history_collection.distinct("user_id", {"session_id": session_id})
455
+ # Debug print 2: Check user_ids
456
+ print("Found user_ids:", user_ids)
457
+
458
+ all_chat_histories = []
459
+ for user_id in user_ids:
460
+ result = get_chat_history(user_id, session_id)
461
+ # Debug print 3: Check each chat history result
462
+ print(f"Chat history for user {user_id}:", "Found" if result else "Not found")
463
+ if result:
464
+ for record in result:
465
+ chat_history = {
466
+ "user_id": record["user_id"], # Convert ObjectId to string
467
+ "session_id": record["session_id"],
468
+ "messages": record["messages"]
469
+ }
470
+ all_chat_histories.append(chat_history)
471
+
472
+ print(all_chat_histories)
473
+
474
+ # Export all chat histories to a JSON file
475
+ # Path: sample_files/chat_histories.json
476
+ # with open("sample_files/all_chat_histories3.json", "w") as file:
477
+ # json.dump(all_chat_histories, file, indent=2)
478
+
479
+ # Debug print 4: Check chat histories
480
+ print("Total chat histories collected:", len(all_chat_histories))
481
+
482
+ # Extract topics with debug print
483
+ # topics = extract_topics_from_materials(session_id)
484
+ # # Export extracted topics to a JSON file
485
+ # with open("sample_files/extracted_topics.json", "w") as file:
486
+ # json.dump(topics, file, indent=2)
487
+
488
+ # Load extracted topics from JSON file
489
+ with open("sample_files/extracted_topics.json", "r") as file:
490
+ topics = json.load(file)
491
+ # Debug print 5: Check topics
492
+ print("Extracted topics:", topics)
493
+
494
+ # Generate analytics
495
+
496
+ analytics_generator = NovaScholarAnalytics()
497
+ analytics = analytics_generator.generate_analytics(all_chat_histories, topics)
498
+ # Debug print 6: Check generated analytics
499
+ print("Generated Analytics:", analytics)
session_page.py CHANGED
@@ -30,6 +30,8 @@ import numpy as np
30
  import re
31
  from analytics import derive_analytics, create_embeddings, cosine_similarity
32
  from bs4 import BeautifulSoup
 
 
33
 
34
  load_dotenv()
35
  MONGO_URI = os.getenv('MONGO_URI')
@@ -39,6 +41,9 @@ client = MongoClient(MONGO_URI)
39
  db = client["novascholar_db"]
40
  polls_collection = db["polls"]
41
  subjective_tests_collection = db["subjective_tests"]
 
 
 
42
  synoptic_store_collection = db["synoptic_store"]
43
 
44
  def get_current_user():
@@ -132,66 +137,6 @@ def get_current_user():
132
  # user = get_current_user()
133
 
134
  def display_preclass_content(session, student_id, course_id):
135
- # """Display pre-class materials for a session"""
136
- # st.subheader("Pre-class Materials")
137
- # print("Session ID is: ", session['session_id'])
138
- # # Display pre-class materials
139
- # materials = resources_collection.find({"session_id": session['session_id']})
140
- # for material in materials:
141
- # with st.expander(f"{material['file_name']} ({material['material_type'].upper()})"):
142
- # file_type = material.get('file_type', 'unknown')
143
- # if file_type == 'application/pdf':
144
- # st.markdown(f"📑 [Open PDF Document]({material['file_name']})")
145
- # if st.button("View PDF", key=f"view_pdf_{material['_id']}"):
146
- # st.text_area("PDF Content", material['text_content'], height=300)
147
- # if st.button("Download PDF", key=f"download_pdf_{material['_id']}"):
148
- # st.download_button(
149
- # label="Download PDF",
150
- # data=material['file_content'],
151
- # file_name=material['file_name'],
152
- # mime='application/pdf'
153
- # )
154
- # if st.button("Mark PDF as Read", key=f"pdf_{material['_id']}"):
155
- # create_notification("PDF marked as read!", "success")
156
- # elif file_type == 'text/plain':
157
- # st.markdown(f"📄 [Open Text Document]({material['file_name']})")
158
- # if st.button("View Text", key=f"view_text_{material['_id']}"):
159
- # st.text_area("Text Content", material['text_content'], height=300)
160
- # if st.button("Download Text", key=f"download_text_{material['_id']}"):
161
- # st.download_button(
162
- # label="Download Text",
163
- # data=material['file_content'],
164
- # file_name=material['file_name'],
165
- # mime='text/plain'
166
- # )
167
- # if st.button("Mark Text as Read", key=f"text_{material['_id']}"):
168
- # create_notification("Text marked as read!", "success")
169
- # elif file_type == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
170
- # st.markdown(f"📄 [Open Word Document]({material['file_name']})")
171
- # if st.button("View Word", key=f"view_word_{material['_id']}"):
172
- # st.text_area("Word Content", material['text_content'], height=300)
173
- # if st.button("Download Word", key=f"download_word_{material['_id']}"):
174
- # st.download_button(
175
- # label="Download Word",
176
- # data=material['file_content'],
177
- # file_name=material['file_name'],
178
- # mime='application/vnd.openxmlformats-officedocument.wordprocessingml.document'
179
- # )
180
- # if st.button("Mark Word as Read", key=f"word_{material['_id']}"):
181
- # create_notification("Word document marked as read!", "success")
182
- # elif file_type == 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
183
- # st.markdown(f"📊 [Open PowerPoint Presentation]({material['file_name']})")
184
- # if st.button("View PowerPoint", key=f"view_pptx_{material['_id']}"):
185
- # st.text_area("PowerPoint Content", material['text_content'], height=300)
186
- # if st.button("Download PowerPoint", key=f"download_pptx_{material['_id']}"):
187
- # st.download_button(
188
- # label="Download PowerPoint",
189
- # data=material['file_content'],
190
- # file_name=material['file_name'],
191
- # mime='application/vnd.openxmlformats-officedocument.presentationml.presentation'
192
- # )
193
- # if st.button("Mark PowerPoint as Read", key=f"pptx_{material['_id']}"):
194
- # create_notification("PowerPoint presentation marked as read!", "success")
195
  """Display pre-class materials for a session including external resources"""
196
  st.subheader("Pre-class Materials")
197
  print("Session ID is: ", session['session_id'])
@@ -203,7 +148,7 @@ def display_preclass_content(session, student_id, course_id):
203
  file_type = material.get('file_type', 'unknown')
204
 
205
  # Handle external resources
206
- if file_type == 'external':
207
  with st.expander(f"📌 {material['file_name']}"):
208
  st.markdown(f"Source: [{material['source_url']}]({material['source_url']})")
209
 
@@ -596,6 +541,247 @@ def display_preclass_content(session, student_id, course_id):
596
  # except Exception as db_error:
597
  # st.error(f"Error saving submission: {str(db_error)}")
598
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
599
 
600
  def extract_youtube_id(url):
601
  """Extract YouTube video ID from URL"""
@@ -611,8 +797,75 @@ def extract_youtube_id(url):
611
  return None
612
  return None
613
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
614
 
615
- def display_in_class_content(session, user_type):
616
  # """Display in-class activities and interactions"""
617
  """Display in-class activities and interactions"""
618
  st.header("In-class Activities")
@@ -625,6 +878,11 @@ def display_in_class_content(session, user_type):
625
  live_polls.display_faculty_interface(session['session_id'])
626
  else:
627
  live_polls.display_student_interface(session['session_id'])
 
 
 
 
 
628
 
629
  def generate_random_assignment_id():
630
  """Generate a random integer ID for assignments"""
@@ -1412,7 +1670,7 @@ def convert_json_to_dict(json_str):
1412
  # with open(r'topics.json', 'r') as file:
1413
  # topics = json.load(file)
1414
 
1415
- def get_preclass_analytics(session):
1416
  # Earlier Code:
1417
  # """Get all user_ids from chat_history collection where session_id matches"""
1418
  # user_ids = chat_history_collection.distinct("user_id", {"session_id": session['session_id']})
@@ -1534,11 +1792,16 @@ def get_preclass_analytics(session):
1534
  print("Fallback analytics returned") # Debug print 8
1535
  return None
1536
  else:
 
 
 
 
 
 
 
1537
  return analytics2
1538
 
1539
 
1540
-
1541
-
1542
  # Load Analytics from a JSON file
1543
  # analytics = []
1544
  # with open(r'new_analytics2.json', 'r') as file:
@@ -1559,7 +1822,7 @@ def display_preclass_analytics2(session, course_id):
1559
  # Initialize or get analytics data from session state
1560
  if 'analytics_data' not in st.session_state:
1561
  # Add debug prints
1562
- analytics_data = get_preclass_analytics(session)
1563
  if analytics_data is None:
1564
  st.info("Fetching new analytics data...")
1565
  if analytics_data is None:
@@ -1962,173 +2225,7 @@ def display_session_analytics(session, course_id):
1962
  # Uploaded on: {material['uploaded_at'].strftime('%Y-%m-%d %H:%M')}
1963
  # """)
1964
 
1965
- def upload_preclass_materials(session_id, course_id):
1966
- """Upload pre-class materials and manage external resources for a session"""
1967
- st.subheader("Pre-class Materials Management")
1968
-
1969
- # Create tabs for different functionalities
1970
- upload_tab, external_tab = st.tabs(["Upload Materials", "External Resources"])
1971
-
1972
- with upload_tab:
1973
- # Original file upload functionality
1974
- uploaded_file = st.file_uploader("Upload Material", type=['txt', 'pdf', 'docx'])
1975
- if uploaded_file is not None:
1976
- with st.spinner("Processing document..."):
1977
- file_name = uploaded_file.name
1978
- file_content = extract_text_from_file(uploaded_file)
1979
- if file_content:
1980
- material_type = st.selectbox("Select Material Type", ["pdf", "docx", "txt"])
1981
- if st.button("Upload Material"):
1982
- upload_resource(course_id, session_id, file_name, uploaded_file, material_type)
1983
- st.success("Material uploaded successfully!")
1984
-
1985
- with external_tab:
1986
- # Fetch and display external resources
1987
- session_data = courses_collection.find_one(
1988
- {"course_id": course_id, "sessions.session_id": session_id},
1989
- {"sessions.$": 1}
1990
- )
1991
-
1992
- if session_data and session_data.get('sessions'):
1993
- session = session_data['sessions'][0]
1994
- external = session.get('external_resources', {})
1995
-
1996
- # Display web articles
1997
- if 'readings' in external:
1998
- st.subheader("Web Articles and Videos")
1999
- for reading in external['readings']:
2000
- col1, col2 = st.columns([3, 1])
2001
- with col1:
2002
- st.markdown(f"**{reading['title']}**")
2003
- st.markdown(f"Type: {reading['type']} | Est. time: {reading['estimated_read_time']}")
2004
- st.markdown(f"URL: [{reading['url']}]({reading['url']})")
2005
- with col2:
2006
- if st.button("Extract Content", key=f"extract_{reading['url']}"):
2007
- with st.spinner("Extracting content..."):
2008
- content = extract_external_content(reading['url'], reading['type'])
2009
- if content:
2010
- resource_id = upload_external_resource(
2011
- course_id,
2012
- session_id,
2013
- reading['title'],
2014
- content,
2015
- reading['type'].lower(),
2016
- reading['url']
2017
- )
2018
- st.success("Content extracted and stored successfully!")
2019
-
2020
- # Display books
2021
- if 'books' in external:
2022
- st.subheader("Recommended Books")
2023
- for book in external['books']:
2024
- st.markdown(f"""
2025
- **{book['title']}** by {book['author']}
2026
- - ISBN: {book['isbn']}
2027
- - Chapters: {book['chapters']}
2028
- """)
2029
-
2030
- # Display additional resources
2031
- if 'additional_resources' in external:
2032
- st.subheader("Additional Resources")
2033
- for resource in external['additional_resources']:
2034
- st.markdown(f"""
2035
- **{resource['title']}** ({resource['type']})
2036
- - {resource['description']}
2037
- - URL: [{resource['url']}]({resource['url']})
2038
- """)
2039
-
2040
- def extract_external_content(url, content_type):
2041
- """Extract content from external resources based on their type"""
2042
- try:
2043
- if content_type.lower() == 'video' and 'youtube.com' in url:
2044
- return extract_youtube_transcript(url)
2045
- else:
2046
- return extract_web_article(url)
2047
- except Exception as e:
2048
- st.error(f"Error extracting content: {str(e)}")
2049
- return None
2050
-
2051
- def extract_youtube_transcript(url):
2052
- """Extract transcript from YouTube videos"""
2053
- try:
2054
- # Extract video ID from URL
2055
- video_id = url.split('v=')[1].split('&')[0]
2056
-
2057
- # Get transcript
2058
- transcript = YouTubeTranscriptApi.get_transcript(video_id)
2059
- # Combine transcript text
2060
- full_text = ' '.join([entry['text'] for entry in transcript])
2061
- return full_text
2062
- except Exception as e:
2063
- st.error(f"Could not extract YouTube transcript: {str(e)}")
2064
- return None
2065
-
2066
- def extract_web_article(url):
2067
- """Extract text content from web articles"""
2068
- try:
2069
- headers = {
2070
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
2071
- }
2072
- response = requests.get(url, headers=headers)
2073
- response.raise_for_status()
2074
-
2075
- soup = BeautifulSoup(response.text, 'html.parser')
2076
-
2077
- # Remove unwanted tags
2078
- for tag in soup(['script', 'style', 'nav', 'footer', 'header']):
2079
- tag.decompose()
2080
-
2081
- # Extract text from paragraphs
2082
- paragraphs = soup.find_all('p')
2083
- text_content = ' '.join([p.get_text().strip() for p in paragraphs])
2084
-
2085
- return text_content
2086
- except Exception as e:
2087
- st.error(f"Could not extract web article content: {str(e)}")
2088
- return None
2089
 
2090
- def upload_external_resource(course_id, session_id, title, content, content_type, source_url):
2091
- """Upload extracted external resource content to the database"""
2092
- resource_data = {
2093
- "_id": ObjectId(),
2094
- "course_id": course_id,
2095
- "session_id": session_id,
2096
- "file_name": f"{title} ({content_type})",
2097
- "file_type": "external",
2098
- "text_content": content,
2099
- "material_type": content_type,
2100
- "source_url": source_url,
2101
- "uploaded_at": datetime.utcnow()
2102
- }
2103
-
2104
- # Check if resource already exists
2105
- existing_resource = resources_collection.find_one({
2106
- "session_id": session_id,
2107
- "source_url": source_url
2108
- })
2109
-
2110
- if existing_resource:
2111
- return existing_resource["_id"]
2112
-
2113
- # Insert new resource
2114
- resources_collection.insert_one(resource_data)
2115
- resource_id = resource_data["_id"]
2116
-
2117
- # Update course document
2118
- courses_collection.update_one(
2119
- {
2120
- "course_id": course_id,
2121
- "sessions.session_id": session_id
2122
- },
2123
- {
2124
- "$push": {"sessions.$.pre_class.resources": resource_id}
2125
- }
2126
- )
2127
-
2128
- if content:
2129
- create_vector_store(content, resource_id)
2130
-
2131
- return resource_id
2132
 
2133
  def display_quiz_tab(student_id, course_id, session_id):
2134
  """Display quizzes for students"""
@@ -2763,6 +2860,8 @@ def display_subjective_test_tab(student_id, course_id, session_id):
2763
  print(f"Error in display_subjective_test_tab: {str(e)}", flush=True)
2764
  st.error("An error occurred while loading the tests. Please try again later.")
2765
 
 
 
2766
  def display_subjective_analysis(test_id, student_id, context):
2767
  """Display subjective test analysis to students and faculty"""
2768
  try:
 
30
  import re
31
  from analytics import derive_analytics, create_embeddings, cosine_similarity
32
  from bs4 import BeautifulSoup
33
+ import streamlit.components.v1 as components
34
+ from live_chat_feature import display_live_chat_interface
35
 
36
  load_dotenv()
37
  MONGO_URI = os.getenv('MONGO_URI')
 
41
  db = client["novascholar_db"]
42
  polls_collection = db["polls"]
43
  subjective_tests_collection = db["subjective_tests"]
44
+ subjective_test_evaluation_collection = db["subjective_test_evaluation"]
45
+ assignment_evaluation_collection = db["assignment_evaluation"]
46
+ subjective_tests_collection = db["subjective_tests"]
47
  synoptic_store_collection = db["synoptic_store"]
48
 
49
  def get_current_user():
 
137
  # user = get_current_user()
138
 
139
  def display_preclass_content(session, student_id, course_id):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  """Display pre-class materials for a session including external resources"""
141
  st.subheader("Pre-class Materials")
142
  print("Session ID is: ", session['session_id'])
 
148
  file_type = material.get('file_type', 'unknown')
149
 
150
  # Handle external resources
151
+ if file_type == 'external' or file_type == 'video':
152
  with st.expander(f"📌 {material['file_name']}"):
153
  st.markdown(f"Source: [{material['source_url']}]({material['source_url']})")
154
 
 
541
  # except Exception as db_error:
542
  # st.error(f"Error saving submission: {str(db_error)}")
543
 
544
+ import requests
545
+
546
+ def fetch_youtube_video_title(video_url):
547
+ """Fetch the title of a YouTube video using the YouTube Data API"""
548
+ api_key = os.getenv("YOUTUBE_API_KEY")
549
+ video_id = extract_youtube_id(video_url)
550
+ if not video_id:
551
+ return None
552
+
553
+ url = f"https://www.googleapis.com/youtube/v3/videos?id={video_id}&key={api_key}&part=snippet"
554
+ response = requests.get(url)
555
+ if response.status_code == 200:
556
+ data = response.json()
557
+ if "items" in data and len(data["items"]) > 0:
558
+ return data["items"][0]["snippet"]["title"]
559
+ return None
560
+
561
+ def upload_video_source(course_id, session_id, video_url):
562
+ """Upload video source and its transcript to the database"""
563
+ # Fetch video title
564
+ video_title = fetch_youtube_video_title(video_url)
565
+ if not video_title:
566
+ st.error("Could not fetch the video title from the provided YouTube URL.")
567
+ return
568
+ # print("Video Title: ", video_title)
569
+ # Extract transcript from YouTube video
570
+ transcript = extract_youtube_transcript(video_url)
571
+
572
+ if not transcript:
573
+ st.error("Could not extract transcript from the provided YouTube URL.")
574
+ return
575
+
576
+ # Create resource document
577
+ resource_data = {
578
+ "_id": ObjectId(),
579
+ "course_id": course_id,
580
+ "session_id": session_id,
581
+ "file_name": video_title,
582
+ "file_type": "video",
583
+ "text_content": transcript,
584
+ "material_type": "video",
585
+ "source_url": video_url,
586
+ "uploaded_at": datetime.utcnow()
587
+ }
588
+ # Check if resource already exists
589
+ existing_resource = resources_collection.find_one({
590
+ "session_id": session_id,
591
+ "source_url": video_url
592
+ })
593
+
594
+ if existing_resource:
595
+ st.warning("This video resource already exists.")
596
+ return existing_resource["_id"]
597
+
598
+ # Insert new resource
599
+ resources_collection.insert_one(resource_data)
600
+ resource_id = resource_data["_id"]
601
+
602
+ # Update course document
603
+ courses_collection.update_one(
604
+ {
605
+ "course_id": course_id,
606
+ "sessions.session_id": session_id
607
+ },
608
+ {
609
+ "$push": {"sessions.$.pre_class.resources": resource_id}
610
+ }
611
+ )
612
+ # Create vector store for the transcript
613
+ create_vector_store(transcript, resource_id)
614
+
615
+ # st.success("Video source uploaded successfully!")
616
+ return resource_id
617
+
618
+ def upload_preclass_materials(session_id, course_id):
619
+ """Upload pre-class materials and manage external resources for a session"""
620
+ st.subheader("Pre-class Materials Management")
621
+
622
+ # Create tabs for different functionalities
623
+ upload_tab, external_tab = st.tabs(["Upload Materials", "External Resources"])
624
+
625
+ with upload_tab:
626
+ # Original file upload functionality
627
+ uploaded_file = st.file_uploader("Upload Material", type=['txt', 'pdf', 'docx'])
628
+ if uploaded_file is not None:
629
+ with st.spinner("Processing document..."):
630
+ file_name = uploaded_file.name
631
+ file_content = extract_text_from_file(uploaded_file)
632
+ if file_content:
633
+ material_type = st.selectbox("Select Material Type", ["pdf", "docx", "txt"])
634
+ if st.button("Upload Material"):
635
+ upload_resource(course_id, session_id, file_name, uploaded_file, material_type)
636
+ st.success("Material uploaded successfully!")
637
+
638
+ with external_tab:
639
+ # Fetch and display external resources
640
+ session_data = courses_collection.find_one(
641
+ {"course_id": course_id, "sessions.session_id": session_id},
642
+ {"sessions.$": 1}
643
+ )
644
+
645
+ if session_data and session_data.get('sessions'):
646
+ session = session_data['sessions'][0]
647
+ external = session.get('external_resources', {})
648
+
649
+ # Display web articles
650
+ if 'readings' in external:
651
+ st.subheader("Web Articles and Videos")
652
+ for reading in external['readings']:
653
+ col1, col2 = st.columns([3, 1])
654
+ with col1:
655
+ st.markdown(f"**{reading['title']}**")
656
+ st.markdown(f"Type: {reading['type']} | Est. time: {reading['estimated_read_time']}")
657
+ st.markdown(f"URL: [{reading['url']}]({reading['url']})")
658
+ with col2:
659
+ if st.button("Extract Content", key=f"extract_{reading['url']}"):
660
+ with st.spinner("Extracting content..."):
661
+ content = extract_external_content(reading['url'], reading['type'])
662
+ if content:
663
+ resource_id = upload_external_resource(
664
+ course_id,
665
+ session_id,
666
+ reading['title'],
667
+ content,
668
+ reading['type'].lower(),
669
+ reading['url']
670
+ )
671
+ st.success("Content extracted and stored successfully!")
672
+
673
+ # Display books
674
+ if 'books' in external:
675
+ st.subheader("Recommended Books")
676
+ for book in external['books']:
677
+ st.markdown(f"""
678
+ **{book['title']}** by {book['author']}
679
+ - ISBN: {book['isbn']}
680
+ - Chapters: {book['chapters']}
681
+ """)
682
+
683
+ # Display additional resources
684
+ if 'additional_resources' in external:
685
+ st.subheader("Additional Resources")
686
+ for resource in external['additional_resources']:
687
+ st.markdown(f"""
688
+ **{resource['title']}** ({resource['type']})
689
+ - {resource['description']}
690
+ - URL: [{resource['url']}]({resource['url']})
691
+ """)
692
+
693
+ def extract_external_content(url, content_type):
694
+ """Extract content from external resources based on their type"""
695
+ try:
696
+ if content_type.lower() == 'video' and 'youtube.com' in url:
697
+ return extract_youtube_transcript(url)
698
+ else:
699
+ return extract_web_article(url)
700
+ except Exception as e:
701
+ st.error(f"Error extracting content: {str(e)}")
702
+ return None
703
+
704
+ def extract_youtube_transcript(url):
705
+ """Extract transcript from YouTube videos"""
706
+ try:
707
+ # Extract video ID from URL
708
+ video_id = url.split('v=')[1].split('&')[0]
709
+
710
+ # Get transcript
711
+ transcript = YouTubeTranscriptApi.get_transcript(video_id)
712
+ # Combine transcript text
713
+ full_text = ' '.join([entry['text'] for entry in transcript])
714
+ return full_text
715
+ except Exception as e:
716
+ st.error(f"Could not extract YouTube transcript: {str(e)}")
717
+ return None
718
+
719
+ def extract_web_article(url):
720
+ """Extract text content from web articles"""
721
+ try:
722
+ headers = {
723
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
724
+ }
725
+ response = requests.get(url, headers=headers)
726
+ response.raise_for_status()
727
+
728
+ soup = BeautifulSoup(response.text, 'html.parser')
729
+
730
+ # Remove unwanted tags
731
+ for tag in soup(['script', 'style', 'nav', 'footer', 'header']):
732
+ tag.decompose()
733
+
734
+ # Extract text from paragraphs
735
+ paragraphs = soup.find_all('p')
736
+ text_content = ' '.join([p.get_text().strip() for p in paragraphs])
737
+
738
+ return text_content
739
+ except Exception as e:
740
+ st.error(f"Could not extract web article content: {str(e)}")
741
+ return None
742
+
743
+ def upload_external_resource(course_id, session_id, title, content, content_type, source_url):
744
+ """Upload extracted external resource content to the database"""
745
+ resource_data = {
746
+ "_id": ObjectId(),
747
+ "course_id": course_id,
748
+ "session_id": session_id,
749
+ "file_name": f"{title} ({content_type})",
750
+ "file_type": "external",
751
+ "text_content": content,
752
+ "material_type": content_type,
753
+ "source_url": source_url,
754
+ "uploaded_at": datetime.utcnow()
755
+ }
756
+
757
+ # Check if resource already exists
758
+ existing_resource = resources_collection.find_one({
759
+ "session_id": session_id,
760
+ "source_url": source_url
761
+ })
762
+
763
+ if existing_resource:
764
+ return existing_resource["_id"]
765
+
766
+ # Insert new resource
767
+ resources_collection.insert_one(resource_data)
768
+ resource_id = resource_data["_id"]
769
+
770
+ # Update course document
771
+ courses_collection.update_one(
772
+ {
773
+ "course_id": course_id,
774
+ "sessions.session_id": session_id
775
+ },
776
+ {
777
+ "$push": {"sessions.$.pre_class.resources": resource_id}
778
+ }
779
+ )
780
+
781
+ if content:
782
+ create_vector_store(content, resource_id)
783
+
784
+ return resource_id
785
 
786
  def extract_youtube_id(url):
787
  """Extract YouTube video ID from URL"""
 
797
  return None
798
  return None
799
 
800
+ def display_live_presentation(session, user_type, course_id):
801
+ st.markdown("### Live Presentation")
802
+
803
+ # Get active presentation
804
+ session_data = courses_collection.find_one(
805
+ {"course_id": course_id, "sessions.session_id": session['session_id']},
806
+ {"sessions.$": 1}
807
+ )
808
+ active_presentation = session_data["sessions"][0].get("in_class", {}).get("active_presentation")
809
+
810
+ # Faculty Interface
811
+ if user_type == 'faculty':
812
+ if not active_presentation:
813
+ st.markdown("""
814
+ <style>
815
+ .url-container {
816
+ background-color: #f8f9fa;
817
+ padding: 20px;
818
+ border-radius: 10px;
819
+ margin: 10px 0;
820
+ }
821
+ .button-container {
822
+ display: flex;
823
+ justify-content: space-between;
824
+ margin-top: 10px;
825
+ }
826
+ </style>
827
+ """, unsafe_allow_html=True)
828
+
829
+ # URL input section
830
+ with st.container():
831
+ ppt_url = st.text_input("🔗 Enter Google Slides Presentation URL",
832
+ placeholder="https://docs.google.com/presentation/...")
833
+
834
+ if ppt_url:
835
+ if st.button("▶️ Activate Presentation",
836
+ use_container_width=True):
837
+ courses_collection.update_one(
838
+ {"course_id": course_id, "sessions.session_id": session['session_id']},
839
+ {"$set": {"sessions.$.in_class.active_presentation": ppt_url}}
840
+ )
841
+ st.success("✅ Presentation activated successfully!")
842
+ st.rerun()
843
+ else:
844
+ # Display active presentation
845
+ st.markdown("#### 🎯 Active Presentation")
846
+ components.iframe(active_presentation, height=800)
847
+
848
+ # Deactivate button
849
+ st.markdown("<div style='margin-top: 20px;'></div>", unsafe_allow_html=True)
850
+ if st.button("⏹️ Deactivate Presentation",
851
+ type="secondary",
852
+ use_container_width=True):
853
+ courses_collection.update_one(
854
+ {"course_id": course_id, "sessions.session_id": session['session_id']},
855
+ {"$unset": {"sessions.$.in_class.active_presentation": ""}}
856
+ )
857
+ st.success("✅ Presentation deactivated successfully!")
858
+ st.rerun()
859
+
860
+ # Student Interface
861
+ else:
862
+ if active_presentation:
863
+ st.markdown("#### 🎯 Active Presentation")
864
+ components.iframe(active_presentation, height=800)
865
+ else:
866
+ st.info("📝 No active presentations at this time.")
867
 
868
+ def display_in_class_content(session, user_type, course_id, user_id):
869
  # """Display in-class activities and interactions"""
870
  """Display in-class activities and interactions"""
871
  st.header("In-class Activities")
 
878
  live_polls.display_faculty_interface(session['session_id'])
879
  else:
880
  live_polls.display_student_interface(session['session_id'])
881
+
882
+ display_live_chat_interface(session, user_id, course_id=course_id)
883
+
884
+ # Live Presentation Feature
885
+ display_live_presentation(session, user_type, course_id)
886
 
887
  def generate_random_assignment_id():
888
  """Generate a random integer ID for assignments"""
 
1670
  # with open(r'topics.json', 'r') as file:
1671
  # topics = json.load(file)
1672
 
1673
+ def get_preclass_analytics(session, course_id):
1674
  # Earlier Code:
1675
  # """Get all user_ids from chat_history collection where session_id matches"""
1676
  # user_ids = chat_history_collection.distinct("user_id", {"session_id": session['session_id']})
 
1792
  print("Fallback analytics returned") # Debug print 8
1793
  return None
1794
  else:
1795
+ try:
1796
+ courses_collection.update_one(
1797
+ {"course_id": course_id, "sessions.session_id": session['session_id']},
1798
+ {"$set": {"sessions.$.pre_class.analytics": analytics2}}
1799
+ )
1800
+ except Exception as e:
1801
+ print("Error storing analytics:", str(e))
1802
  return analytics2
1803
 
1804
 
 
 
1805
  # Load Analytics from a JSON file
1806
  # analytics = []
1807
  # with open(r'new_analytics2.json', 'r') as file:
 
1822
  # Initialize or get analytics data from session state
1823
  if 'analytics_data' not in st.session_state:
1824
  # Add debug prints
1825
+ analytics_data = get_preclass_analytics(session, course)
1826
  if analytics_data is None:
1827
  st.info("Fetching new analytics data...")
1828
  if analytics_data is None:
 
2225
  # Uploaded on: {material['uploaded_at'].strftime('%Y-%m-%d %H:%M')}
2226
  # """)
2227
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2228
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2229
 
2230
  def display_quiz_tab(student_id, course_id, session_id):
2231
  """Display quizzes for students"""
 
2860
  print(f"Error in display_subjective_test_tab: {str(e)}", flush=True)
2861
  st.error("An error occurred while loading the tests. Please try again later.")
2862
 
2863
+
2864
+
2865
  def display_subjective_analysis(test_id, student_id, context):
2866
  """Display subjective test analysis to students and faculty"""
2867
  try: