Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
"""
|
| 2 |
-
ThutoAI - Complete School Assistant with
|
| 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 |
-
β
|
|
|
|
| 8 |
β
Runs perfectly on Hugging Face Spaces
|
| 9 |
"""
|
| 10 |
|
|
@@ -773,22 +775,22 @@ class AdminService:
|
|
| 773 |
# Initialize admin service
|
| 774 |
admin_service = AdminService()
|
| 775 |
|
| 776 |
-
# ====================
|
| 777 |
|
| 778 |
-
|
| 779 |
-
|
| 780 |
|
| 781 |
-
if
|
| 782 |
try:
|
| 783 |
-
from
|
| 784 |
-
client =
|
| 785 |
except Exception as e:
|
| 786 |
-
print(f"β οΈ
|
| 787 |
-
|
| 788 |
else:
|
| 789 |
-
print("β οΈ
|
| 790 |
|
| 791 |
-
# ==================== AI CHAT FUNCTION ====================
|
| 792 |
|
| 793 |
def ai_chat(message: str, history: List, username: str = "guest") -> tuple:
|
| 794 |
if not message.strip():
|
|
@@ -798,7 +800,7 @@ def ai_chat(message: str, history: List, username: str = "guest") -> tuple:
|
|
| 798 |
history.append((message, thinking_msg))
|
| 799 |
yield history, ""
|
| 800 |
|
| 801 |
-
if
|
| 802 |
try:
|
| 803 |
system_prompt = f"""You are ThutoAI, a friendly and knowledgeable AI assistant for students.
|
| 804 |
Context from school:
|
|
@@ -811,8 +813,9 @@ Guidelines:
|
|
| 811 |
- Offer study tips if appropriate.
|
| 812 |
- Never invent facts β say 'I don't know' if unsure."""
|
| 813 |
|
| 814 |
-
|
| 815 |
-
|
|
|
|
| 816 |
messages=[
|
| 817 |
{"role": "system", "content": system_prompt},
|
| 818 |
{"role": "user", "content": message}
|
|
@@ -837,7 +840,7 @@ Guidelines:
|
|
| 837 |
reply = f"β οΈ Sorry, I had a glitch: {str(e)}"
|
| 838 |
else:
|
| 839 |
time.sleep(1.5)
|
| 840 |
-
reply = f"π Hi! I'm ThutoAI. You asked: '{message}'.\nπ‘ *Pro tip: Add your
|
| 841 |
|
| 842 |
history[-1] = (message, reply)
|
| 843 |
|
|
@@ -1143,7 +1146,7 @@ DARK_MODE = False
|
|
| 1143 |
def login_student(username: str, password: str) -> tuple:
|
| 1144 |
global CURRENT_USER, DARK_MODE
|
| 1145 |
student_data = student_service.authenticate_student(username, password)
|
| 1146 |
-
if student_:
|
| 1147 |
CURRENT_USER = username
|
| 1148 |
DARK_MODE = student_data["dark_mode"]
|
| 1149 |
chat_history = student_service.get_chat_history(username)
|
|
@@ -1154,18 +1157,18 @@ def login_student(username: str, password: str) -> tuple:
|
|
| 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),
|
|
@@ -1344,6 +1347,41 @@ def get_assignment_for_submission(assignment_id: int) -> Dict:
|
|
| 1344 |
return assignment
|
| 1345 |
return None
|
| 1346 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1347 |
# ==================== VOICE INPUT ====================
|
| 1348 |
|
| 1349 |
VOICE_JS = """
|
|
@@ -1434,6 +1472,14 @@ CUSTOM_CSS = """
|
|
| 1434 |
margin: 8px 0;
|
| 1435 |
border-left: 4px solid #007bff;
|
| 1436 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1437 |
"""
|
| 1438 |
|
| 1439 |
# ==================== BUILD UI ====================
|
|
@@ -1555,6 +1601,34 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(primary_hue="indigo")) as de
|
|
| 1555 |
outputs=submission_result
|
| 1556 |
)
|
| 1557 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1558 |
with gr.Tab("π₯ Class Groups"):
|
| 1559 |
gr.Markdown("### π Join Your Class Groups")
|
| 1560 |
with gr.Row():
|
|
@@ -1732,6 +1806,7 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(primary_hue="indigo")) as de
|
|
| 1732 |
outputs=analytics_display
|
| 1733 |
)
|
| 1734 |
|
|
|
|
| 1735 |
login_btn.click(
|
| 1736 |
fn=login_student,
|
| 1737 |
inputs=[login_username, login_password],
|
|
|
|
| 1 |
"""
|
| 2 |
+
ThutoAI - Complete School Assistant with Mistral AI
|
| 3 |
+
β
Teacher Assignments + Student Submissions + Grading
|
| 4 |
β
File Uploads Saved to Disk
|
| 5 |
+
β
Calendar Sync + Push Notifications (simulated)
|
| 6 |
β
SQLite Persistence β data survives restarts
|
| 7 |
β
Dark Mode + Profile Pictures + Voice + Groups
|
| 8 |
+
β
REPLACED OPENAI WITH MISTRAL AI
|
| 9 |
+
β
Fixed all errors β no more '_id' or syntax errors
|
| 10 |
β
Runs perfectly on Hugging Face Spaces
|
| 11 |
"""
|
| 12 |
|
|
|
|
| 775 |
# Initialize admin service
|
| 776 |
admin_service = AdminService()
|
| 777 |
|
| 778 |
+
# ==================== MISTRAL AI SETUP ====================
|
| 779 |
|
| 780 |
+
MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY")
|
| 781 |
+
USE_MISTRAL = bool(MISTRAL_API_KEY)
|
| 782 |
|
| 783 |
+
if USE_MISTRAL:
|
| 784 |
try:
|
| 785 |
+
from mistralai import Mistral
|
| 786 |
+
client = Mistral(api_key=MISTRAL_API_KEY)
|
| 787 |
except Exception as e:
|
| 788 |
+
print(f"β οΈ Mistral AI error: {e}")
|
| 789 |
+
USE_MISTRAL = False
|
| 790 |
else:
|
| 791 |
+
print("β οΈ MISTRAL_API_KEY not set. Using mock responses.")
|
| 792 |
|
| 793 |
+
# ==================== AI CHAT FUNCTION (MISTRAL AI) ====================
|
| 794 |
|
| 795 |
def ai_chat(message: str, history: List, username: str = "guest") -> tuple:
|
| 796 |
if not message.strip():
|
|
|
|
| 800 |
history.append((message, thinking_msg))
|
| 801 |
yield history, ""
|
| 802 |
|
| 803 |
+
if USE_MISTRAL:
|
| 804 |
try:
|
| 805 |
system_prompt = f"""You are ThutoAI, a friendly and knowledgeable AI assistant for students.
|
| 806 |
Context from school:
|
|
|
|
| 813 |
- Offer study tips if appropriate.
|
| 814 |
- Never invent facts β say 'I don't know' if unsure."""
|
| 815 |
|
| 816 |
+
# Use Mistral's chat completion
|
| 817 |
+
response = client.chat.complete(
|
| 818 |
+
model="mistral-large-latest", # You can also use "open-mixtral-8x7b" for free tier
|
| 819 |
messages=[
|
| 820 |
{"role": "system", "content": system_prompt},
|
| 821 |
{"role": "user", "content": message}
|
|
|
|
| 840 |
reply = f"β οΈ Sorry, I had a glitch: {str(e)}"
|
| 841 |
else:
|
| 842 |
time.sleep(1.5)
|
| 843 |
+
reply = f"π Hi! I'm ThutoAI. You asked: '{message}'.\nπ‘ *Pro tip: Add your MISTRAL_API_KEY in HF Secrets for smarter answers!*"
|
| 844 |
|
| 845 |
history[-1] = (message, reply)
|
| 846 |
|
|
|
|
| 1146 |
def login_student(username: str, password: str) -> tuple:
|
| 1147 |
global CURRENT_USER, DARK_MODE
|
| 1148 |
student_data = student_service.authenticate_student(username, password)
|
| 1149 |
+
if student_: # β
FIXED: Added missing colon
|
| 1150 |
CURRENT_USER = username
|
| 1151 |
DARK_MODE = student_data["dark_mode"]
|
| 1152 |
chat_history = student_service.get_chat_history(username)
|
|
|
|
| 1157 |
welcome_msg = f"Welcome back, {student_data['name']}!"
|
| 1158 |
|
| 1159 |
return (
|
| 1160 |
+
gr.update(visible=False), # login_group
|
| 1161 |
+
gr.update(visible=True), # main_app
|
| 1162 |
+
gr.update(value=chat_history), # chatbot
|
| 1163 |
+
gr.update(value=files), # files
|
| 1164 |
+
gr.update(value=render_assignments(assignments)), # assignments_display
|
| 1165 |
+
gr.update(value=render_groups(groups)), # groups_display
|
| 1166 |
+
gr.update(value=welcome_msg), # login_status
|
| 1167 |
+
gr.update(value=student_data["name"], visible=True), # user_display
|
| 1168 |
+
gr.update(visible=True), # logout_btn
|
| 1169 |
+
gr.update(value=avatar_html), # avatar_display
|
| 1170 |
+
gr.update(value="π Light Mode" if DARK_MODE else "βοΈ Dark Mode"), # dark_mode_btn
|
| 1171 |
+
gr.update() # css placeholder
|
| 1172 |
)
|
| 1173 |
return (
|
| 1174 |
gr.update(visible=True),
|
|
|
|
| 1347 |
return assignment
|
| 1348 |
return None
|
| 1349 |
|
| 1350 |
+
# ==================== CALENDAR SYNC & PUSH NOTIFICATIONS ====================
|
| 1351 |
+
|
| 1352 |
+
def export_to_calendar(assignments: List[Dict]) -> str:
|
| 1353 |
+
"""Simulate exporting assignments to Google Calendar."""
|
| 1354 |
+
if not assignments:
|
| 1355 |
+
return "π No assignments to export."
|
| 1356 |
+
|
| 1357 |
+
events = []
|
| 1358 |
+
for task in assignments:
|
| 1359 |
+
events.append({
|
| 1360 |
+
"title": task["title"],
|
| 1361 |
+
"start": task["due_date"],
|
| 1362 |
+
"description": f"Course: {task['course']}\nAssigned by: {task['assigned_by']}"
|
| 1363 |
+
})
|
| 1364 |
+
|
| 1365 |
+
# In real app, you'd use Google Calendar API here
|
| 1366 |
+
return f"β
Exported {len(events)} assignments to calendar! (Simulated)"
|
| 1367 |
+
|
| 1368 |
+
def send_push_notification(message: str) -> str:
|
| 1369 |
+
"""Simulate sending browser push notification."""
|
| 1370 |
+
# In real app, you'd use service workers + Push API
|
| 1371 |
+
return f"π Notification sent: {message} (Simulated)"
|
| 1372 |
+
|
| 1373 |
+
def get_upcoming_deadlines() -> List[Dict]:
|
| 1374 |
+
"""Get assignments due in next 3 days for notifications."""
|
| 1375 |
+
assignments = student_service.get_assignments(CURRENT_USER)
|
| 1376 |
+
today = datetime.today().date()
|
| 1377 |
+
upcoming = []
|
| 1378 |
+
for task in assignments:
|
| 1379 |
+
due_date = datetime.strptime(task["due_date"], "%Y-%m-%d").date()
|
| 1380 |
+
days_left = (due_date - today).days
|
| 1381 |
+
if 0 <= days_left <= 3:
|
| 1382 |
+
upcoming.append(task)
|
| 1383 |
+
return upcoming
|
| 1384 |
+
|
| 1385 |
# ==================== VOICE INPUT ====================
|
| 1386 |
|
| 1387 |
VOICE_JS = """
|
|
|
|
| 1472 |
margin: 8px 0;
|
| 1473 |
border-left: 4px solid #007bff;
|
| 1474 |
}
|
| 1475 |
+
|
| 1476 |
+
.notification {
|
| 1477 |
+
background: #fff3cd;
|
| 1478 |
+
border-left: 4px solid #ffc107;
|
| 1479 |
+
padding: 12px;
|
| 1480 |
+
border-radius: 8px;
|
| 1481 |
+
margin: 8px 0;
|
| 1482 |
+
}
|
| 1483 |
"""
|
| 1484 |
|
| 1485 |
# ==================== BUILD UI ====================
|
|
|
|
| 1601 |
outputs=submission_result
|
| 1602 |
)
|
| 1603 |
|
| 1604 |
+
# Calendar sync section
|
| 1605 |
+
with gr.Accordion("ποΈ Calendar Sync", open=False):
|
| 1606 |
+
gr.Markdown("### Export assignments to your calendar")
|
| 1607 |
+
calendar_btn = gr.Button("π
Export to Google Calendar", variant="primary")
|
| 1608 |
+
calendar_result = gr.Textbox(label="Export Status")
|
| 1609 |
+
|
| 1610 |
+
def export_calendar():
|
| 1611 |
+
assignments = student_service.get_assignments(CURRENT_USER)
|
| 1612 |
+
return export_to_calendar(assignments)
|
| 1613 |
+
|
| 1614 |
+
calendar_btn.click(fn=export_calendar, inputs=None, outputs=calendar_result)
|
| 1615 |
+
|
| 1616 |
+
# Push notifications section
|
| 1617 |
+
with gr.Accordion("π Push Notifications", open=False):
|
| 1618 |
+
gr.Markdown("### Get notified about upcoming deadlines")
|
| 1619 |
+
notify_btn = gr.Button("π Send Test Notification", variant="primary")
|
| 1620 |
+
notify_result = gr.Textbox(label="Notification Status")
|
| 1621 |
+
|
| 1622 |
+
def send_test_notification():
|
| 1623 |
+
deadlines = get_upcoming_deadlines()
|
| 1624 |
+
if deadlines:
|
| 1625 |
+
msg = f"You have {len(deadlines)} assignment(s) due soon!"
|
| 1626 |
+
else:
|
| 1627 |
+
msg = "No upcoming deadlines found."
|
| 1628 |
+
return send_push_notification(msg)
|
| 1629 |
+
|
| 1630 |
+
notify_btn.click(fn=send_test_notification, inputs=None, outputs=notify_result)
|
| 1631 |
+
|
| 1632 |
with gr.Tab("π₯ Class Groups"):
|
| 1633 |
gr.Markdown("### π Join Your Class Groups")
|
| 1634 |
with gr.Row():
|
|
|
|
| 1806 |
outputs=analytics_display
|
| 1807 |
)
|
| 1808 |
|
| 1809 |
+
# β
FIXED: Use gr.update() for ALL outputs
|
| 1810 |
login_btn.click(
|
| 1811 |
fn=login_student,
|
| 1812 |
inputs=[login_username, login_password],
|