File size: 8,608 Bytes
0aa6283 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | import os
import streamlit as st
from phase.Student_view import chatbot, lesson, quiz
from utils import db as dbapi
import utils.api as api # <-- backend Space client
USE_LOCAL_DB = os.getenv("DISABLE_DB", "1") != "1"
# --- Load external CSS ---
def load_css(file_name: str):
try:
with open(file_name, "r", encoding="utf-8") as f:
st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
except FileNotFoundError:
st.warning("β οΈ Stylesheet not found. Please ensure 'assets/styles.css' exists.")
def show_student_dashboard():
# Load CSS
css_path = os.path.join("assets", "styles.css")
load_css(css_path)
# Current user
user = st.session_state.user
name = user["name"]
student_id = user["user_id"]
# --- Real metrics from DB ---
# Requires helper funcs in utils/db.py: user_xp_and_level, recent_lessons_for_student, list_assignments_for_student
if USE_LOCAL_DB and hasattr(dbapi, "user_xp_and_level"):
stats = dbapi.user_xp_and_level(student_id)
else:
# Try backend; fall back to defaults if not available yet
try:
stats = api.user_stats(student_id)
except Exception:
stats = {"xp": 0, "level": 1, "streak": 0}
xp = int(stats.get("xp", 0))
level = int(stats.get("level", 1))
study_streak = int(stats.get("streak", 0))
# # Cap for the visual bar
# max_xp = max(500, ((xp // 500) + 1) * 500)
# Assignments for βMy Workβ
if USE_LOCAL_DB and hasattr(dbapi, "list_assignments_for_student"):
rows = dbapi.list_assignments_for_student(student_id)
else:
try:
rows = api.list_assignments_for_student(student_id)
except Exception:
rows = []
def _pct_from_row(r: dict):
sp = r.get("score_pct")
if sp is not None:
try:
return int(round(float(sp)))
except Exception:
pass
s, t = r.get("score"), r.get("total")
if s is not None and t not in (None, 0):
try:
return int(round((float(s) / float(t)) * 100))
except Exception:
return None
return None
if USE_LOCAL_DB and hasattr(dbapi, "student_quiz_average"):
quiz_score = dbapi.student_quiz_average(student_id)
else:
try:
quiz_score = api.student_quiz_average(student_id)
except Exception:
quiz_score = 0
lessons_completed = sum(1 for r in rows if r.get("status") == "completed" or _pct_from_row(r) == 100)
total_lessons = len(rows)
# Recent lessons assigned to this student
if USE_LOCAL_DB and hasattr(dbapi, "recent_lessons_for_student"):
recent_lessons = dbapi.recent_lessons_for_student(student_id, limit=5)
else:
try:
recent_lessons = api.recent_lessons_for_student(student_id, limit=5)
except Exception:
recent_lessons = []
# Daily Challenge derived from real data
challenge_difficulty = "Easy" if level < 3 else ("Medium" if level < 6 else "Hard")
challenge_title = "Complete 1 quiz with 80%+"
challenge_desc = "Prove you remember yesterday's key points."
challenge_progress = 100 if quiz_score >= 80 else 0
reward = "+50 XP"
time_left = "Ends 11:59 PM"
# Achievements from real data
achievements = [
{"title": "First Steps", "desc": "Complete your first lesson", "earned": lessons_completed > 0},
{"title": "Quiz Whiz", "desc": "Score 80%+ on any quiz", "earned": quiz_score >= 80},
{"title": "On a Roll", "desc": "Study 3 days in a row", "earned": study_streak >= 3},
{"title": "Consistency", "desc": "Finish 5 assignments", "earned": total_lessons >= 5 and lessons_completed >= 5},
]
# --- Welcome Card ---
st.markdown(
f"""
<div class="welcome-card">
<h2>Welcome back, {name}!</h2>
<p style="font-size: 20px;">{"Ready to continue your financial journey?" if lessons_completed > 0 else "Start your financial journey."}</p>
</div>
""",
unsafe_allow_html=True
)
st.write("")
# --- Quick Action Buttons ---
actions = [
("π Start a Lesson", "Lessons"),
("π Attempt a Quiz", "Quiz"),
("π¬ Talk to AI Tutor", "Chatbot"),
]
# 5 columns: spacer, button, button, button, spacer
cols = st.columns([1, 2, 2, 2, 1])
for i, (label, page) in enumerate(actions):
with cols[i+1]: # skip the left spacer
if st.button(label, key=f"action_{i}"):
st.session_state.current_page = page
st.rerun()
st.write("")
# --- Progress Summary Cards ---
progress_cols = st.columns(3)
progress_cols[0].metric("π Lessons Completed", f"{lessons_completed}/{total_lessons}")
progress_cols[1].metric("π Quiz Score", f"{quiz_score}/100")
progress_cols[2].metric("π₯ Study Streak", f"{study_streak} days")
st.write("")
# --- XP Bar ---
# stats already fetched above
xp = int(stats.get("xp", 0))
level = int(stats.get("level", 1))
study_streak = int(stats.get("streak", 0))
# prefer server-provided per-level fields
into = int(stats.get("into", -1))
need = int(stats.get("need", -1))
# fallback if backend hasn't been updated to include into/need yet
if into < 0 or need <= 0:
base = 500
level = max(1, xp // base + 1)
start = (level - 1) * base
into = xp - start
need = base
if into == need:
level += 1
into = 0
cap = max(500, ((xp // 500) + 1) * 500) # next threshold
pct = 0 if cap <= 0 else min(100, int(round(100 * xp / cap)))
st.markdown(
f"""
<div class="xp-card">
<span class="xp-level">Level {level}</span>
<span class="xp-text">{xp:,} / {cap:,} XP</span>
<div class="xp-bar">
<div class="xp-fill" style="width: {pct}%;"></div>
</div>
<div class="xp-total">Total XP: {xp:,}</div>
</div>
""",
unsafe_allow_html=True
)
# pct = 0 if max_xp <= 0 else min(100, int(round((xp / max_xp) * 100)))
# st.markdown(
# f"""
# <div class="xp-card">
# <span class="xp-level">Level {level}</span>
# <span class="xp-text">{xp} / {max_xp} XP</span>
# <div class="xp-bar">
# <div class="xp-fill" style="width: {pct}%;"></div>
# </div>
# </div>
# """,
# unsafe_allow_html=True
# )
# st.write("")
# --- My Assignments (from DB) ---
st.markdown("---")
st.subheader("π My Work")
if not rows:
st.info("No assignments yet. Ask your teacher to assign a lesson.")
else:
for a in rows:
title = a.get("title", "Untitled")
subj = a.get("subject", "General")
lvl = a.get("level", "Beginner")
status = a.get("status", "not_started")
due = a.get("due_at")
due_txt = f" Β· Due {str(due)[:10]}" if due else ""
st.markdown(f"**{title}** Β· {subj} Β· {lvl}{due_txt}")
st.caption(f"Status: {status} Β· Resume at section {a.get('current_pos', 1)}")
st.markdown("---")
# --- Recent Lessons & Achievements ---
col1, col2 = st.columns(2)
def _progress_value(v):
try:
f = float(v)
except Exception:
return 0.0
# streamlit accepts 0β1 float; if someone passes 0β100, scale it
return max(0.0, min(1.0, f if f <= 1.0 else f / 100.0))
with col1:
st.subheader("π Recent Lessons")
st.caption("Continue where you left off")
if not recent_lessons:
st.info("No recent lessons yet.")
else:
for lesson in recent_lessons:
prog = lesson.get("progress", 0)
st.progress(_progress_value(prog))
status = "β
Complete" if (isinstance(prog, (int, float)) and prog >= 100) else f"{int(prog)}% complete"
st.write(f"**{lesson.get('title','Untitled Lesson')}** β {status}")
with col2:
st.subheader("π Achievements")
st.caption("Your learning milestones")
for ach in achievements:
if ach["earned"]:
st.success(f"β {ach['title']} β {ach['desc']}")
else:
st.info(f"π {ach['title']} β {ach['desc']}")
|