Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
"""
|
| 2 |
-
ThutoAI - Complete School Assistant with
|
| 3 |
-
β
Teacher Assignments +
|
| 4 |
-
β
|
| 5 |
-
β
|
| 6 |
-
β
|
| 7 |
-
β
Fixed event
|
| 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
|
| 48 |
cursor.execute('''
|
| 49 |
CREATE TABLE IF NOT EXISTS student_sessions (
|
| 50 |
username TEXT PRIMARY KEY,
|
| 51 |
-
chat_history TEXT,
|
| 52 |
-
files TEXT,
|
| 53 |
-
assignments TEXT,
|
| 54 |
-
groups TEXT,
|
| 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,
|
| 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),
|
| 1053 |
-
gr.update(visible=True),
|
| 1054 |
-
gr.update(value=chat_history),
|
| 1055 |
-
gr.update(value=files),
|
| 1056 |
-
gr.update(value=render_assignments(assignments)),
|
| 1057 |
-
gr.update(value=render_groups(groups)),
|
| 1058 |
-
gr.update(value=welcome_msg),
|
| 1059 |
-
gr.update(value=student_data["name"], visible=True),
|
| 1060 |
-
gr.update(visible=True),
|
| 1061 |
-
gr.update(value=avatar_html),
|
| 1062 |
-
gr.update(value="π Light Mode" if DARK_MODE else "βοΈ Dark Mode"),
|
| 1063 |
-
gr.update()
|
| 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 |
-
|
| 1116 |
-
#
|
| 1117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 1138 |
-
|
| 1139 |
-
|
| 1140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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],
|