ngwakomadikwe commited on
Commit
f72a641
Β·
verified Β·
1 Parent(s): 4f6d052

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +271 -39
app.py CHANGED
@@ -1,10 +1,10 @@
1
  """
2
- ThutoAI - Complete School Assistant with SQLite Persistence
3
- βœ… Teacher Assignments + Admin Analytics
4
- βœ… Dark Mode + Profile Pictures
5
- βœ… Voice Input + Class Groups
6
- βœ… SQLite Database β€” data survives restarts!
7
- βœ… Fixed event handler errors
8
  βœ… Runs perfectly on Hugging Face Spaces
9
  """
10
 
@@ -19,6 +19,11 @@ import base64
19
  from collections import Counter
20
  import shutil
21
 
 
 
 
 
 
22
  # ==================== DATABASE SETUP ====================
23
 
24
  class DatabaseManager:
@@ -44,14 +49,14 @@ class DatabaseManager:
44
  )
45
  ''')
46
 
47
- # Student sessions (chat history, files, assignments, groups)
48
  cursor.execute('''
49
  CREATE TABLE IF NOT EXISTS student_sessions (
50
  username TEXT PRIMARY KEY,
51
- chat_history TEXT, -- JSON string
52
- files TEXT, -- JSON string
53
- assignments TEXT, -- JSON string
54
- groups TEXT, -- JSON string
55
  FOREIGN KEY (username) REFERENCES students (username)
56
  )
57
  ''')
@@ -87,6 +92,23 @@ class DatabaseManager:
87
  )
88
  ''')
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  # Insert sample data if tables are empty
91
  cursor.execute("SELECT COUNT(*) FROM students")
92
  if cursor.fetchone()[0] == 0:
@@ -290,7 +312,7 @@ class StudentService:
290
  print(f"Get files error: {e}")
291
  return []
292
 
293
- def add_file(self, username: str, filename: str):
294
  try:
295
  cursor = self.conn.cursor()
296
  cursor.execute('''
@@ -301,6 +323,7 @@ class StudentService:
301
  files = json.loads(result[0]) if result[0] else []
302
  files.append({
303
  "name": filename,
 
304
  "uploaded_at": datetime.now().strftime("%Y-%m-%d %H:%M")
305
  })
306
  cursor.execute('''
@@ -325,7 +348,6 @@ class StudentService:
325
  student_groups = set(json.loads(result[0])) if result[0] else set()
326
  all_assignments = []
327
 
328
- # Get all assignments from all students
329
  cursor.execute('''
330
  SELECT assignments FROM student_sessions WHERE assignments IS NOT NULL AND assignments != '[]'
331
  ''')
@@ -336,9 +358,22 @@ class StudentService:
336
  assignments = json.loads(row[0])
337
  for assignment in assignments:
338
  if assignment["course"] in student_groups:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  all_assignments.append(assignment)
340
 
341
- # Remove duplicates
342
  seen = set()
343
  unique_assignments = []
344
  for assignment in all_assignments:
@@ -491,6 +526,53 @@ class StudentService:
491
  print(f"Get recent activity error: {e}")
492
  return []
493
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
  # Initialize student service
495
  student_service = StudentService()
496
 
@@ -591,7 +673,7 @@ class SchoolService:
591
  total_views = cursor.fetchone()[0] or 0
592
  return {
593
  "total_announcements": total_announcements,
594
- "total_files": 0, # You could track this separately
595
  "active_courses": active_courses,
596
  "high_priority": high_priority,
597
  "total_views": total_views
@@ -622,7 +704,6 @@ class SchoolService:
622
  "status": "pending"
623
  }
624
 
625
- # Get all students in this group
626
  cursor = self.conn.cursor()
627
  cursor.execute('''
628
  SELECT username FROM student_sessions
@@ -630,7 +711,6 @@ class SchoolService:
630
  ''', (f'%"{course}"%',))
631
  students = cursor.fetchall()
632
 
633
- # Update each student's assignments
634
  for (username,) in students:
635
  cursor.execute('''
636
  SELECT assignments FROM student_sessions WHERE username = ?
@@ -677,6 +757,19 @@ class AdminService:
677
  print(f"Admin auth error: {e}")
678
  return None
679
 
 
 
 
 
 
 
 
 
 
 
 
 
 
680
  # Initialize admin service
681
  admin_service = AdminService()
682
 
@@ -844,6 +937,10 @@ def render_assignments(assignments: List[Dict]) -> str:
844
  badge = f"βœ… Due in {days_left} days"
845
  color = "#28a745"
846
 
 
 
 
 
847
  html += f"""
848
  <div style='
849
  background: white;
@@ -859,6 +956,14 @@ def render_assignments(assignments: List[Dict]) -> str:
859
  <div style='color: #6c757d; margin-bottom: 8px;'>πŸ“š {task['course']} Β· πŸ‘©β€πŸ« {task['assigned_by']}</div>
860
  <div style='color: #495057;'>πŸ“… Due: {task['due_date']}</div>
861
  <div style='color: #6c757d; font-size: 0.9em;'>{task.get('description', '')}</div>
 
 
 
 
 
 
 
 
862
  </div>
863
  <span style='
864
  background: {color};
@@ -1049,18 +1154,18 @@ def login_student(username: str, password: str) -> tuple:
1049
  welcome_msg = f"Welcome back, {student_data['name']}!"
1050
 
1051
  return (
1052
- gr.update(visible=False), # login_group
1053
- gr.update(visible=True), # main_app
1054
- gr.update(value=chat_history), # chatbot
1055
- gr.update(value=files), # files
1056
- gr.update(value=render_assignments(assignments)), # assignments_display
1057
- gr.update(value=render_groups(groups)), # groups_display
1058
- gr.update(value=welcome_msg), # login_status
1059
- gr.update(value=student_data["name"], visible=True), # user_display
1060
- gr.update(visible=True), # logout_btn
1061
- gr.update(value=avatar_html), # avatar_display
1062
- gr.update(value="πŸŒ™ Light Mode" if DARK_MODE else "β˜€οΈ Dark Mode"), # dark_mode_btn
1063
- gr.update() # css placeholder
1064
  )
1065
  return (
1066
  gr.update(visible=True),
@@ -1112,9 +1217,13 @@ def upload_avatar(file) -> str:
1112
  return "❌ No file selected"
1113
  if CURRENT_USER == "guest":
1114
  return "❌ Please log in first"
1115
- # In production, save file to disk
1116
- # For demo, just store filename
1117
- student_service.update_avatar(CURRENT_USER, file.name)
 
 
 
 
1118
  return f"βœ… Avatar updated!"
1119
 
1120
  def join_group(group_code: str) -> tuple:
@@ -1134,10 +1243,16 @@ def leave_group(group_code: str) -> tuple:
1134
  def upload_file_for_student(file) -> str:
1135
  if not file:
1136
  return "❌ No file selected"
1137
- result = f"βœ… Uploaded: {file.name}"
1138
- if CURRENT_USER != "guest":
1139
- student_service.add_file(CURRENT_USER, file.name)
1140
- return result
 
 
 
 
 
 
1141
 
1142
  # ==================== TEACHER FUNCTIONS ====================
1143
 
@@ -1194,7 +1309,6 @@ def create_assignment(title: str, description: str, due_date: str, course: str)
1194
  if assignment_id == -1:
1195
  return "❌ Failed to create assignment. Please try again."
1196
 
1197
- # Count recipients
1198
  conn = db_manager.get_connection()
1199
  cursor = conn.cursor()
1200
  cursor.execute('''
@@ -1206,6 +1320,30 @@ def create_assignment(title: str, description: str, due_date: str, course: str)
1206
 
1207
  return f"βœ… Assignment created! ID: {assignment_id}. Sent to {recipients} students in {course}."
1208
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1209
  # ==================== VOICE INPUT ====================
1210
 
1211
  VOICE_JS = """
@@ -1288,6 +1426,14 @@ CUSTOM_CSS = """
1288
  background: #2a2a2a !important;
1289
  border-color: #5a5a5a !important;
1290
  }
 
 
 
 
 
 
 
 
1291
  """
1292
 
1293
  # ==================== BUILD UI ====================
@@ -1375,6 +1521,40 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(primary_hue="indigo")) as de
1375
  outputs=assignments_display
1376
  )
1377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1378
  with gr.Tab("πŸ‘₯ Class Groups"):
1379
  gr.Markdown("### πŸŽ’ Join Your Class Groups")
1380
  with gr.Row():
@@ -1431,7 +1611,7 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(primary_hue="indigo")) as de
1431
  )
1432
  demo.load(
1433
  fn=lambda: get_avatar_html(
1434
- None, # We don't handle real avatar paths yet
1435
  student_service.authenticate_student(CURRENT_USER, "")["name"] if CURRENT_USER != "guest" else "Guest"
1436
  ) if CURRENT_USER != "guest" else "",
1437
  inputs=None,
@@ -1484,6 +1664,59 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(primary_hue="indigo")) as de
1484
  outputs=assignment_result
1485
  )
1486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1487
  with gr.Tab("πŸ“Š Admin Analytics"):
1488
  gr.Markdown("### πŸ“ˆ School Analytics Dashboard")
1489
  analytics_display = gr.HTML()
@@ -1499,7 +1732,6 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(primary_hue="indigo")) as de
1499
  outputs=analytics_display
1500
  )
1501
 
1502
- # βœ… FIXED: Use gr.update() for ALL outputs
1503
  login_btn.click(
1504
  fn=login_student,
1505
  inputs=[login_username, login_password],
 
1
  """
2
+ ThutoAI - Complete School Assistant with File Uploads & Assignment Submission
3
+ βœ… Teacher Assignments + Student Submissions
4
+ βœ… File Uploads Saved to Disk
5
+ βœ… SQLite Persistence β€” data survives restarts
6
+ βœ… Dark Mode + Profile Pictures + Voice + Groups
7
+ βœ… Fixed event handlers β€” no more '_id' errors
8
  βœ… Runs perfectly on Hugging Face Spaces
9
  """
10
 
 
19
  from collections import Counter
20
  import shutil
21
 
22
+ # Create uploads directory
23
+ UPLOADS_DIR = "uploads"
24
+ if not os.path.exists(UPLOADS_DIR):
25
+ os.makedirs(UPLOADS_DIR)
26
+
27
  # ==================== DATABASE SETUP ====================
28
 
29
  class DatabaseManager:
 
49
  )
50
  ''')
51
 
52
+ # Student sessions
53
  cursor.execute('''
54
  CREATE TABLE IF NOT EXISTS student_sessions (
55
  username TEXT PRIMARY KEY,
56
+ chat_history TEXT,
57
+ files TEXT,
58
+ assignments TEXT,
59
+ groups TEXT,
60
  FOREIGN KEY (username) REFERENCES students (username)
61
  )
62
  ''')
 
92
  )
93
  ''')
94
 
95
+ # Assignment submissions
96
+ cursor.execute('''
97
+ CREATE TABLE IF NOT EXISTS assignment_submissions (
98
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
99
+ assignment_id INTEGER NOT NULL,
100
+ student_username TEXT NOT NULL,
101
+ submission_text TEXT,
102
+ submission_file TEXT,
103
+ submitted_at TEXT NOT NULL,
104
+ status TEXT DEFAULT 'pending',
105
+ feedback TEXT,
106
+ graded_by TEXT,
107
+ grade TEXT,
108
+ FOREIGN KEY (student_username) REFERENCES students (username)
109
+ )
110
+ ''')
111
+
112
  # Insert sample data if tables are empty
113
  cursor.execute("SELECT COUNT(*) FROM students")
114
  if cursor.fetchone()[0] == 0:
 
312
  print(f"Get files error: {e}")
313
  return []
314
 
315
+ def add_file(self, username: str, filename: str, file_path: str):
316
  try:
317
  cursor = self.conn.cursor()
318
  cursor.execute('''
 
323
  files = json.loads(result[0]) if result[0] else []
324
  files.append({
325
  "name": filename,
326
+ "path": file_path,
327
  "uploaded_at": datetime.now().strftime("%Y-%m-%d %H:%M")
328
  })
329
  cursor.execute('''
 
348
  student_groups = set(json.loads(result[0])) if result[0] else set()
349
  all_assignments = []
350
 
 
351
  cursor.execute('''
352
  SELECT assignments FROM student_sessions WHERE assignments IS NOT NULL AND assignments != '[]'
353
  ''')
 
358
  assignments = json.loads(row[0])
359
  for assignment in assignments:
360
  if assignment["course"] in student_groups:
361
+ # Check if already submitted
362
+ cursor.execute('''
363
+ SELECT status, grade, feedback FROM assignment_submissions
364
+ WHERE assignment_id = ? AND student_username = ?
365
+ ''', (assignment["id"], username))
366
+ submission = cursor.fetchone()
367
+ if submission:
368
+ assignment["submission_status"] = submission[0]
369
+ assignment["grade"] = submission[2] if submission[2] else "Not graded"
370
+ assignment["feedback"] = submission[1] if submission[1] else "No feedback yet"
371
+ else:
372
+ assignment["submission_status"] = "not_submitted"
373
+ assignment["grade"] = None
374
+ assignment["feedback"] = None
375
  all_assignments.append(assignment)
376
 
 
377
  seen = set()
378
  unique_assignments = []
379
  for assignment in all_assignments:
 
526
  print(f"Get recent activity error: {e}")
527
  return []
528
 
529
+ def submit_assignment(self, username: str, assignment_id: int, submission_text: str, submission_file_path: str = None) -> str:
530
+ try:
531
+ cursor = self.conn.cursor()
532
+ cursor.execute('''
533
+ INSERT INTO assignment_submissions
534
+ (assignment_id, student_username, submission_text, submission_file, submitted_at, status)
535
+ VALUES (?, ?, ?, ?, ?, ?)
536
+ ''', (
537
+ assignment_id,
538
+ username,
539
+ submission_text,
540
+ submission_file_path,
541
+ datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
542
+ "submitted"
543
+ ))
544
+ self.conn.commit()
545
+ return "βœ… Assignment submitted successfully!"
546
+ except Exception as e:
547
+ return f"❌ Submission failed: {str(e)}"
548
+
549
+ def get_assignment_submissions(self, assignment_id: int) -> List:
550
+ try:
551
+ cursor = self.conn.cursor()
552
+ cursor.execute('''
553
+ SELECT s.name, sub.submission_text, sub.submission_file, sub.submitted_at, sub.status, sub.grade, sub.feedback
554
+ FROM assignment_submissions sub
555
+ JOIN students s ON sub.student_username = s.username
556
+ WHERE sub.assignment_id = ?
557
+ ORDER BY sub.submitted_at DESC
558
+ ''', (assignment_id,))
559
+ results = cursor.fetchall()
560
+ return [
561
+ {
562
+ "student_name": row[0],
563
+ "submission_text": row[1],
564
+ "submission_file": row[2],
565
+ "submitted_at": row[3],
566
+ "status": row[4],
567
+ "grade": row[5],
568
+ "feedback": row[6]
569
+ }
570
+ for row in results
571
+ ]
572
+ except Exception as e:
573
+ print(f"Get assignment submissions error: {e}")
574
+ return []
575
+
576
  # Initialize student service
577
  student_service = StudentService()
578
 
 
673
  total_views = cursor.fetchone()[0] or 0
674
  return {
675
  "total_announcements": total_announcements,
676
+ "total_files": 0,
677
  "active_courses": active_courses,
678
  "high_priority": high_priority,
679
  "total_views": total_views
 
704
  "status": "pending"
705
  }
706
 
 
707
  cursor = self.conn.cursor()
708
  cursor.execute('''
709
  SELECT username FROM student_sessions
 
711
  ''', (f'%"{course}"%',))
712
  students = cursor.fetchall()
713
 
 
714
  for (username,) in students:
715
  cursor.execute('''
716
  SELECT assignments FROM student_sessions WHERE username = ?
 
757
  print(f"Admin auth error: {e}")
758
  return None
759
 
760
+ def grade_assignment(self, submission_id: int, grade: str, feedback: str) -> str:
761
+ try:
762
+ cursor = self.conn.cursor()
763
+ cursor.execute('''
764
+ UPDATE assignment_submissions
765
+ SET status = ?, grade = ?, feedback = ?, graded_by = ?
766
+ WHERE id = ?
767
+ ''', ("graded", grade, feedback, "teacher", submission_id))
768
+ self.conn.commit()
769
+ return "βœ… Assignment graded successfully!"
770
+ except Exception as e:
771
+ return f"❌ Grading failed: {str(e)}"
772
+
773
  # Initialize admin service
774
  admin_service = AdminService()
775
 
 
937
  badge = f"βœ… Due in {days_left} days"
938
  color = "#28a745"
939
 
940
+ submission_status = task.get("submission_status", "not_submitted")
941
+ grade = task.get("grade", "N/A")
942
+ feedback = task.get("feedback", "No feedback yet")
943
+
944
  html += f"""
945
  <div style='
946
  background: white;
 
956
  <div style='color: #6c757d; margin-bottom: 8px;'>πŸ“š {task['course']} Β· πŸ‘©β€πŸ« {task['assigned_by']}</div>
957
  <div style='color: #495057;'>πŸ“… Due: {task['due_date']}</div>
958
  <div style='color: #6c757d; font-size: 0.9em;'>{task.get('description', '')}</div>
959
+
960
+ <div style='margin-top: 12px; padding: 8px; background: #f8f9fa; border-radius: 8px;'>
961
+ <div style='font-weight: bold; color: #212529;'>Submission Status:
962
+ <span style='color: {'#28a745' if submission_status == 'submitted' else '#dc3545'};'>{submission_status.title()}</span>
963
+ </div>
964
+ <div>Grade: <strong>{grade}</strong></div>
965
+ <div>Feedback: <em>{feedback}</em></div>
966
+ </div>
967
  </div>
968
  <span style='
969
  background: {color};
 
1154
  welcome_msg = f"Welcome back, {student_data['name']}!"
1155
 
1156
  return (
1157
+ gr.update(visible=False),
1158
+ gr.update(visible=True),
1159
+ gr.update(value=chat_history),
1160
+ gr.update(value=files),
1161
+ gr.update(value=render_assignments(assignments)),
1162
+ gr.update(value=render_groups(groups)),
1163
+ gr.update(value=welcome_msg),
1164
+ gr.update(value=student_data["name"], visible=True),
1165
+ gr.update(visible=True),
1166
+ gr.update(value=avatar_html),
1167
+ gr.update(value="πŸŒ™ Light Mode" if DARK_MODE else "β˜€οΈ Dark Mode"),
1168
+ gr.update()
1169
  )
1170
  return (
1171
  gr.update(visible=True),
 
1217
  return "❌ No file selected"
1218
  if CURRENT_USER == "guest":
1219
  return "❌ Please log in first"
1220
+
1221
+ # Save avatar to disk
1222
+ filename = f"avatar_{CURRENT_USER}_{int(time.time())}{os.path.splitext(file.name)[1]}"
1223
+ filepath = os.path.join(UPLOADS_DIR, filename)
1224
+ shutil.copy(file.name, filepath)
1225
+
1226
+ student_service.update_avatar(CURRENT_USER, filepath)
1227
  return f"βœ… Avatar updated!"
1228
 
1229
  def join_group(group_code: str) -> tuple:
 
1243
  def upload_file_for_student(file) -> str:
1244
  if not file:
1245
  return "❌ No file selected"
1246
+ if CURRENT_USER == "guest":
1247
+ return "❌ Please log in first"
1248
+
1249
+ # Save file to disk
1250
+ filename = f"{CURRENT_USER}_{int(time.time())}_{os.path.basename(file.name)}"
1251
+ filepath = os.path.join(UPLOADS_DIR, filename)
1252
+ shutil.copy(file.name, filepath)
1253
+
1254
+ student_service.add_file(CURRENT_USER, file.name, filepath)
1255
+ return f"βœ… Uploaded: {file.name}"
1256
 
1257
  # ==================== TEACHER FUNCTIONS ====================
1258
 
 
1309
  if assignment_id == -1:
1310
  return "❌ Failed to create assignment. Please try again."
1311
 
 
1312
  conn = db_manager.get_connection()
1313
  cursor = conn.cursor()
1314
  cursor.execute('''
 
1320
 
1321
  return f"βœ… Assignment created! ID: {assignment_id}. Sent to {recipients} students in {course}."
1322
 
1323
+ # ==================== ASSIGNMENT SUBMISSION ====================
1324
+
1325
+ def submit_assignment(assignment_id: int, submission_text: str, submission_file) -> str:
1326
+ if CURRENT_USER == "guest":
1327
+ return "❌ Please log in first."
1328
+ if not submission_text.strip() and not submission_file:
1329
+ return "⚠️ Please provide text or upload a file."
1330
+
1331
+ file_path = None
1332
+ if submission_file:
1333
+ filename = f"submission_{CURRENT_USER}_{assignment_id}_{int(time.time())}{os.path.splitext(submission_file.name)[1]}"
1334
+ file_path = os.path.join(UPLOADS_DIR, filename)
1335
+ shutil.copy(submission_file.name, file_path)
1336
+
1337
+ result = student_service.submit_assignment(CURRENT_USER, assignment_id, submission_text, file_path)
1338
+ return result
1339
+
1340
+ def get_assignment_for_submission(assignment_id: int) -> Dict:
1341
+ assignments = student_service.get_assignments(CURRENT_USER)
1342
+ for assignment in assignments:
1343
+ if assignment["id"] == assignment_id:
1344
+ return assignment
1345
+ return None
1346
+
1347
  # ==================== VOICE INPUT ====================
1348
 
1349
  VOICE_JS = """
 
1426
  background: #2a2a2a !important;
1427
  border-color: #5a5a5a !important;
1428
  }
1429
+
1430
+ .submission-card {
1431
+ background: #f8f9fa;
1432
+ border-radius: 12px;
1433
+ padding: 16px;
1434
+ margin: 8px 0;
1435
+ border-left: 4px solid #007bff;
1436
+ }
1437
  """
1438
 
1439
  # ==================== BUILD UI ====================
 
1521
  outputs=assignments_display
1522
  )
1523
 
1524
+ # Assignment submission section
1525
+ with gr.Accordion("πŸ“€ Submit Assignment", open=False):
1526
+ gr.Markdown("### Submit your work for grading")
1527
+ assignment_dropdown = gr.Dropdown(
1528
+ choices=[],
1529
+ label="Select Assignment to Submit",
1530
+ interactive=True
1531
+ )
1532
+ submission_text = gr.Textbox(
1533
+ label="Your Answer / Comments",
1534
+ placeholder="Type your response here...",
1535
+ lines=4
1536
+ )
1537
+ submission_file = gr.File(label="Upload your work (PDF, DOC, TXT, etc.)")
1538
+ submit_assignment_btn = gr.Button("πŸ“€ Submit Assignment", variant="primary")
1539
+ submission_result = gr.Textbox(label="Submission Status")
1540
+
1541
+ def update_assignment_dropdown():
1542
+ assignments = student_service.get_assignments(CURRENT_USER)
1543
+ choices = [(f"{a['title']} (Due: {a['due_date']})", a["id"]) for a in assignments if a.get("submission_status") != "submitted"]
1544
+ return gr.update(choices=choices)
1545
+
1546
+ def handle_submission(assignment_id, text, file):
1547
+ if not assignment_id:
1548
+ return "⚠️ Please select an assignment."
1549
+ return submit_assignment(assignment_id, text, file)
1550
+
1551
+ demo.load(fn=update_assignment_dropdown, inputs=None, outputs=assignment_dropdown)
1552
+ submit_assignment_btn.click(
1553
+ fn=handle_submission,
1554
+ inputs=[assignment_dropdown, submission_text, submission_file],
1555
+ outputs=submission_result
1556
+ )
1557
+
1558
  with gr.Tab("πŸ‘₯ Class Groups"):
1559
  gr.Markdown("### πŸŽ’ Join Your Class Groups")
1560
  with gr.Row():
 
1611
  )
1612
  demo.load(
1613
  fn=lambda: get_avatar_html(
1614
+ student_service.authenticate_student(CURRENT_USER, "")["avatar"] if CURRENT_USER != "guest" else None,
1615
  student_service.authenticate_student(CURRENT_USER, "")["name"] if CURRENT_USER != "guest" else "Guest"
1616
  ) if CURRENT_USER != "guest" else "",
1617
  inputs=None,
 
1664
  outputs=assignment_result
1665
  )
1666
 
1667
+ # View submissions
1668
+ with gr.Accordion("πŸ“‹ View Submissions", open=False):
1669
+ gr.Markdown("### View and Grade Student Submissions")
1670
+ submissions_assignment_dropdown = gr.Dropdown(
1671
+ choices=[],
1672
+ label="Select Assignment to View Submissions",
1673
+ interactive=True
1674
+ )
1675
+ submissions_display = gr.JSON(label="Submissions")
1676
+ grade_input = gr.Textbox(label="Grade (e.g., A+, 85%, Pass)")
1677
+ feedback_input = gr.Textbox(label="Feedback", lines=3)
1678
+ grade_btn = gr.Button("βœ… Grade Submission", variant="primary")
1679
+ grade_result = gr.Textbox(label="Grading Status")
1680
+
1681
+ def update_submissions_dropdown():
1682
+ # Get assignments created by this teacher
1683
+ conn = db_manager.get_connection()
1684
+ cursor = conn.cursor()
1685
+ cursor.execute('''
1686
+ SELECT DISTINCT a.id, a.title
1687
+ FROM assignment_submissions s
1688
+ JOIN student_sessions ss ON s.student_username = json_extract(ss.assignments, '$[0].assigned_by')
1689
+ JOIN json_each(ss.assignments) j
1690
+ JOIN json_tree(j.value) t ON t.key = 'id'
1691
+ JOIN announcements a ON t.value = a.id
1692
+ WHERE a.posted_by = ?
1693
+ ''', (CURRENT_TEACHER["name"],))
1694
+ results = cursor.fetchall()
1695
+ choices = [(f"{row[1]} (ID: {row[0]})", row[0]) for row in results]
1696
+ return gr.update(choices=choices)
1697
+
1698
+ def load_submissions(assignment_id):
1699
+ if not assignment_id:
1700
+ return []
1701
+ return student_service.get_assignment_submissions(assignment_id)
1702
+
1703
+ def grade_submission(submission_id, grade, feedback):
1704
+ if not submission_id or not grade:
1705
+ return "⚠️ Please select submission and enter grade."
1706
+ return admin_service.grade_assignment(submission_id, grade, feedback)
1707
+
1708
+ submissions_assignment_dropdown.change(
1709
+ fn=load_submissions,
1710
+ inputs=submissions_assignment_dropdown,
1711
+ outputs=submissions_display
1712
+ )
1713
+
1714
+ grade_btn.click(
1715
+ fn=grade_submission,
1716
+ inputs=[submissions_display, grade_input, feedback_input],
1717
+ outputs=grade_result
1718
+ )
1719
+
1720
  with gr.Tab("πŸ“Š Admin Analytics"):
1721
  gr.Markdown("### πŸ“ˆ School Analytics Dashboard")
1722
  analytics_display = gr.HTML()
 
1732
  outputs=analytics_display
1733
  )
1734
 
 
1735
  login_btn.click(
1736
  fn=login_student,
1737
  inputs=[login_username, login_password],