import time
import streamlit as st
import pandas as pd
from content_generator import generate_content
from quiz_generator import generate_quiz
from evaluation import evaluate_answers
from flashcard_generator import generate_flashcards
from tutor import get_tutor_reply
from pdf_export import export_study_notes_pdf, export_quiz_results_pdf
from gamification import (
load_gamification, save_gamification,
update_streak, award_xp, check_and_award_badges,
get_level, get_xp_for_quiz, record_quiz,
BADGES, XP_STUDY_SESSION, XP_FLASHCARD_DECK,
)
from utils import (
get_topics, save_progress, load_progress,
get_weak_topics, get_learning_path,
load_notes, save_note, delete_note,
)
# ── Page config ──────────────────────────────────────────────────────────────
st.set_page_config(
page_title="LearnCraft – Personalized Learning",
page_icon="🎓",
layout="wide",
initial_sidebar_state="expanded",
)
# ── Light Theme CSS ──────────────────────────────────────────────────────────
st.markdown("""
""", unsafe_allow_html=True)
# ── Session state init ────────────────────────────────────────────────────────
defaults = {
"page": "home",
"content": None,
"quiz": None,
"answers": {},
"submitted": False,
"progress": load_progress(),
"quiz_start_time": None,
"quiz_elapsed": None,
"flashcards": [],
"fc_index": 0,
"fc_flipped": False,
"daily_goal": 3,
"sessions_today": 0,
# Gamification
"gami": load_gamification(),
"new_badges": [],
"xp_gained": 0,
"level_up_msg": None,
# Tutor chat
"tutor_messages": [],
"tutor_topic": "",
}
for k, v in defaults.items():
if k not in st.session_state:
st.session_state[k] = v
# ── Sidebar ───────────────────────────────────────────────────────────────────
with st.sidebar:
st.markdown("""
🎓
LearnCraft
Personalized Learning Platform
""", unsafe_allow_html=True)
pages = {
"🏠 Home": "home",
"📚 Study Content": "study",
"🃏 Flashcards": "flashcards",
"🧩 Take Quiz": "quiz",
"🤖 AI Tutor": "tutor",
"🏅 Achievements": "achievements",
"📊 My Progress": "progress",
"📝 My Notes": "notes",
}
for label, key in pages.items():
is_active = st.session_state.page == key
btn_label = f"▶ {label}" if is_active else label
if st.button(btn_label, key=f"nav_{key}", use_container_width=True):
st.session_state.page = key
st.session_state.submitted = False
st.rerun()
st.markdown("---")
# XP & Level display
gami = st.session_state.gami
xp = gami.get("xp", 0)
streak = gami.get("streak", 0)
level_name, next_level_name, xp_to_next, level_pct = get_level(xp)
badges_earned = len(gami.get("badges", []))
st.markdown(f"""
Your Level
{level_name}
{xp} XP {f"· {xp_to_next} to {next_level_name}" if next_level_name else "· MAX LEVEL"}
""", unsafe_allow_html=True)
# Daily goal tracker
progress = st.session_state.progress
sessions_today = sum(
1 for s in progress.get("sessions", [])
if s.get("date") == str(__import__("datetime").date.today())
)
goal = st.session_state.daily_goal
goal_pct = min(sessions_today / goal, 1.0)
st.markdown(f"""
Today's Goal
{sessions_today} / {goal} sessions
""", unsafe_allow_html=True)
st.progress(goal_pct)
st.markdown("", unsafe_allow_html=True)
topics_count = len(set(progress.get("topics_studied", [])))
best_score = progress.get("best_score", 0)
scores = progress.get("scores", [])
avg_score = round(sum(scores)/len(scores), 1) if scores else 0
st.markdown(f"""
""", unsafe_allow_html=True)
# ── Page routing ──────────────────────────────────────────────────────────────
page = st.session_state.page
# ══════════════════════ HOME ══════════════════════════════════════════════════
if page == "home":
st.markdown("""
AI-Powered Learning
LearnCraft
Generate personalized study material, flashcards & quizzes
tailored exactly to your level and learning goals.
""", unsafe_allow_html=True)
weak = get_weak_topics(st.session_state.progress)
if weak:
st.warning(f"📌 **Recommended Review:** You scored below 60% on: {', '.join(weak)}. Consider revisiting these topics!")
c1, c2, c3, c4 = st.columns(4)
features = [
("📖", "Smart Content", "AI-generated study notes adapted to Beginner, Intermediate, or Advanced levels in multiple styles."),
("🃏", "Flashcards", "Flip-card revision sessions with auto-generated front/back cards. Perfect for memorisation."),
("🧩", "Custom Quizzes", "MCQ, True/False, Fill-in-the-Blank and Short Answer quizzes with timed mode."),
("📊", "Analytics", "Score history charts, per-topic bests, streak tracking and weak-topic detection."),
]
for col, (icon, title, desc) in zip([c1, c2, c3, c4], features):
with col:
st.markdown(f"""
""", unsafe_allow_html=True)
st.markdown("---")
st.markdown("### 🚀 Quick Start")
qa, qb, qc = st.columns(3)
with qa:
if st.button("📚 Generate Study Notes", use_container_width=True):
st.session_state.page = "study"; st.rerun()
with qb:
if st.button("🃏 Practice Flashcards", use_container_width=True):
st.session_state.page = "flashcards"; st.rerun()
with qc:
if st.button("🧩 Start a Quiz", use_container_width=True):
st.session_state.page = "quiz"; st.rerun()
# Recent activity
sessions = st.session_state.progress.get("sessions", [])
if sessions:
st.markdown("---")
st.markdown("### 🕐 Recent Activity")
recent = sessions[-5:][::-1]
for s in recent:
score = s.get("score", 0)
color = "#10b981" if score >= 80 else "#f97316" if score >= 60 else "#ef4444"
st.markdown(f"""
{s.get("topic","Unknown")}
{s.get("date","")}
{score}%
""", unsafe_allow_html=True)
# ══════════════════════ STUDY ════════════════════════════════════════════════
elif page == "study":
st.markdown("## 📚 Study Content Generator")
st.markdown("Choose a topic and level to get personalized AI-generated study notes.
", unsafe_allow_html=True)
col1, col2 = st.columns([2, 1])
with col1:
topic = st.text_input("📌 Topic", placeholder="e.g. Photosynthesis, World War II, Python Functions…")
custom_focus = st.text_area("🎯 Focus area (optional)", placeholder="e.g. focus on the Calvin cycle, or key dates and battles…", height=75)
with col2:
level = st.selectbox("🎓 Your Level", ["Beginner", "Intermediate", "Advanced"])
content_type = st.selectbox("📄 Content Style", ["Summary Notes", "Detailed Explanation", "Bullet Points", "Concept Map"])
if topic:
path = get_learning_path(topic)
if path:
path_html = " → ".join(
f"{s}" if s.lower() == topic.lower()
else f"{s}"
for s in path
)
st.markdown(f"""
🗺️ Suggested Path: {path_html}
""", unsafe_allow_html=True)
if st.button("✨ Generate Content", use_container_width=True):
if not topic.strip():
st.warning("Please enter a topic first.")
else:
with st.spinner("Crafting your personalized content…"):
content = generate_content(topic, level, content_type, custom_focus)
st.session_state.content = content
save_progress(st.session_state.progress, topic=topic.title())
# Gamification: award XP for study session
gami = update_streak(st.session_state.gami)
gami, xp_amt, lvl_up = award_xp(gami, XP_STUDY_SESSION, "study_session")
topics_count = len(set(st.session_state.progress.get("topics_studied", [])))
new_b = check_and_award_badges(gami, {"topics_count": topics_count, "event": ""})
save_gamification(gami)
st.session_state.gami = gami
if new_b:
st.session_state.new_badges = new_b
if lvl_up:
st.session_state.level_up_msg = lvl_up
if st.session_state.content:
st.markdown("---")
data = st.session_state.content
m1, m2, m3 = st.columns(3)
m1.metric("Topic", data["topic"])
m2.metric("Level", data["level"])
m3.metric("Est. Read Time", data["read_time"])
if data.get("ai_generated"):
st.info("✨ Content is AI-generated and tailored to your topic and level.")
for section in data["sections"]:
st.markdown(f"""
📌 {section['title']}
{section['content']}
""", unsafe_allow_html=True)
if data.get("key_terms"):
st.markdown("### 🔑 Key Terms")
cols = st.columns(3)
for i, term in enumerate(data["key_terms"]):
with cols[i % 3]:
st.markdown(f"""
{term['term']}
{term['definition']}
""", unsafe_allow_html=True)
if data.get("summary"):
st.markdown("### 💡 Quick Summary")
st.info(data["summary"])
st.markdown("---")
col_note, col_quiz, col_flash = st.columns(3)
with col_note:
st.markdown("##### 📝 Save a Note")
note_text = st.text_area("Note", placeholder="Something to remember…", height=75, label_visibility="collapsed")
if st.button("💾 Save Note"):
if note_text.strip():
save_note(note_text, data["topic"])
# Badge for first note
gami = st.session_state.gami
new_b = check_and_award_badges(gami, {"event": "note_saved"})
if new_b:
save_gamification(gami)
st.session_state.gami = gami
st.session_state.new_badges = new_b
st.success("Note saved!")
else:
st.warning("Write something first.")
with col_quiz:
st.markdown("##### 🧩 Test Yourself")
st.markdown("Take a quiz on this exact topic.
", unsafe_allow_html=True)
if st.button("🧩 Start Quiz on This Topic", use_container_width=True):
st.session_state.page = "quiz"
st.session_state.quiz_topic = data["topic"]
st.session_state.quiz_level = data["level"]
st.session_state.submitted = False
st.rerun()
with col_flash:
st.markdown("##### 🃏 Flashcard Mode")
st.markdown("Generate flip-cards for quick revision.
", unsafe_allow_html=True)
if st.button("🃏 Generate Flashcards", use_container_width=True):
st.session_state.fc_topic = data["topic"]
st.session_state.fc_level = data["level"]
st.session_state.page = "flashcards"
st.rerun()
# PDF Export
st.markdown("---")
st.markdown("##### 📄 Export as PDF")
if st.button("⬇️ Download Study Notes PDF", use_container_width=True):
with st.spinner("Generating PDF…"):
pdf_bytes = export_study_notes_pdf(data)
st.download_button(
label="📥 Click to Download PDF",
data=pdf_bytes,
file_name=f"LearnCraft_{data['topic'].replace(' ','_')}_Notes.pdf",
mime="application/pdf",
use_container_width=True,
)
# ══════════════════════ FLASHCARDS ══════════════════════════════════════════
elif page == "flashcards":
st.markdown("## 🃏 Flashcard Studio")
st.markdown("Click any card to flip it and reveal the answer.
", unsafe_allow_html=True)
col1, col2 = st.columns([2, 1])
with col1:
default_topic = getattr(st.session_state, "fc_topic", "")
fc_topic = st.text_input("📌 Topic", value=default_topic, placeholder="e.g. Quantum Mechanics, World War II…")
with col2:
default_level = getattr(st.session_state, "fc_level", "Intermediate")
fc_level = st.selectbox("🎓 Level", ["Beginner", "Intermediate", "Advanced"],
index=["Beginner", "Intermediate", "Advanced"].index(default_level))
num_cards = st.slider("Number of Cards", min_value=5, max_value=20, value=10)
if st.button("🃏 Generate Flashcards", use_container_width=True):
if not fc_topic.strip():
st.warning("Please enter a topic.")
else:
with st.spinner("Creating your flashcard set…"):
cards = generate_flashcards(fc_topic, fc_level, num_cards)
st.session_state.flashcards = cards
st.session_state.fc_index = 0
st.session_state.fc_flipped = False
# Gamification: XP for flashcard deck
gami = update_streak(st.session_state.gami)
gami, xp_amt, lvl_up = award_xp(gami, XP_FLASHCARD_DECK, "flashcard_deck")
new_b = check_and_award_badges(gami, {"event": "flashcards"})
save_gamification(gami)
st.session_state.gami = gami
if new_b:
st.session_state.new_badges = new_b
if lvl_up:
st.session_state.level_up_msg = lvl_up
cards = st.session_state.flashcards
if cards:
st.markdown("---")
idx = st.session_state.fc_index
total_fc = len(cards)
card = cards[idx]
flipped = st.session_state.fc_flipped
# Progress
st.markdown(f"""
Card {idx+1} of {total_fc}
{round((idx+1)/total_fc*100)}% through deck
""", unsafe_allow_html=True)
st.progress((idx + 1) / total_fc)
# Flip card (CSS-based)
flipped_class = "flipped" if flipped else ""
st.markdown(f"""
""", unsafe_allow_html=True)
st.markdown("")
nav1, nav2, nav3 = st.columns([1, 2, 1])
with nav1:
if st.button("⬅ Previous", use_container_width=True, disabled=(idx == 0)):
st.session_state.fc_index = idx - 1
st.session_state.fc_flipped = False
st.rerun()
with nav2:
if st.button("🔄 Flip Card", use_container_width=True):
st.session_state.fc_flipped = not st.session_state.fc_flipped
st.rerun()
with nav3:
if st.button("Next ➡", use_container_width=True, disabled=(idx == total_fc - 1)):
st.session_state.fc_index = idx + 1
st.session_state.fc_flipped = False
st.rerun()
# Mini deck overview
st.markdown("---")
st.markdown("### 📋 All Cards in This Deck")
for i, c in enumerate(cards):
bg = "#ede9fe" if i == idx else "#fff"
bd = "#6c47ff" if i == idx else "#e2ddf5"
st.markdown(f"""
""", unsafe_allow_html=True)
# ══════════════════════ QUIZ ════════════════════════════════════════════════
elif page == "quiz":
st.markdown("## 🧩 Quiz Generator")
if not st.session_state.submitted:
st.markdown("Configure your quiz, then test your knowledge!
", unsafe_allow_html=True)
col1, col2 = st.columns([2, 1])
with col1:
default_topic = getattr(st.session_state, "quiz_topic", "")
topic = st.text_input("📌 Topic", value=default_topic, placeholder="e.g. Machine Learning, Calculus…")
with col2:
default_level = getattr(st.session_state, "quiz_level", "Intermediate")
level = st.selectbox("🎓 Difficulty", ["Beginner", "Intermediate", "Advanced"],
index=["Beginner", "Intermediate", "Advanced"].index(default_level))
col3, col4, col5 = st.columns(3)
with col3:
num_q = st.number_input("Number of Questions", min_value=3, max_value=15, value=5)
with col4:
q_type = st.selectbox("Question Type", ["Mixed", "Multiple Choice", "True/False", "Short Answer", "Fill in the Blank"])
with col5:
time_limit = st.selectbox("⏱️ Time Limit", ["No limit", "5 minutes", "10 minutes", "15 minutes"])
if st.button("🎲 Generate Quiz", use_container_width=True):
if not topic.strip():
st.warning("Please enter a topic.")
else:
with st.spinner("Building your quiz…"):
quiz = generate_quiz(topic, level, num_q, q_type)
st.session_state.quiz = quiz
st.session_state.answers = {}
st.session_state.submitted = False
st.session_state.quiz_start_time = time.time()
st.session_state.time_limit = time_limit
st.session_state.quiz_elapsed = None
if st.session_state.quiz and not st.session_state.submitted:
quiz = st.session_state.quiz
if st.session_state.quiz_start_time:
elapsed = int(time.time() - st.session_state.quiz_start_time)
limit = st.session_state.get("time_limit", "No limit")
if limit != "No limit":
limit_secs = int(limit.split()[0]) * 60
remaining = limit_secs - elapsed
if remaining <= 0:
st.error("⏱️ Time's up! Submitting…")
st.session_state.quiz_elapsed = elapsed
st.session_state.submitted = True
st.rerun()
else:
r_m, r_s = divmod(remaining, 60)
st.info(f"⏱️ Time remaining: {r_m}m {r_s}s")
else:
m, s = divmod(elapsed, 60)
st.info(f"⏱️ Elapsed: {m}m {s}s")
st.markdown("---")
st.markdown(f"### 📝 {quiz['title']}")
st.markdown(f"{len(quiz['questions'])} questions · {quiz['difficulty']} · {quiz['topic']}
", unsafe_allow_html=True)
for i, q in enumerate(quiz["questions"]):
st.markdown(f"""
Question {i+1} · {q['type']}
{q['question']}
""", unsafe_allow_html=True)
if q["type"] in ("Multiple Choice", "True/False"):
ans = st.radio(f"Answer Q{i+1}", q["options"], key=f"q_{i}", label_visibility="collapsed")
st.session_state.answers[i] = ans
elif q["type"] == "Fill in the Blank":
ans = st.text_input(f"Blank Q{i+1}", key=f"q_{i}", label_visibility="collapsed", placeholder="Type the missing word…")
st.session_state.answers[i] = ans
else:
ans = st.text_input(f"Answer Q{i+1}", key=f"q_{i}", label_visibility="collapsed", placeholder="Type your answer…")
st.session_state.answers[i] = ans
st.markdown("")
if st.button("✅ Submit Quiz", use_container_width=True):
st.session_state.quiz_elapsed = int(time.time() - st.session_state.quiz_start_time)
st.session_state.submitted = True
st.rerun()
else:
# ── Results ──────────────────────────────────────────────────────────
quiz = st.session_state.quiz
results = evaluate_answers(quiz, st.session_state.answers)
score = results["score_percent"]
elapsed = st.session_state.get("quiz_elapsed", 0) or 0
em, es = divmod(elapsed, 60)
st.markdown(f"""
Quiz Complete
{score}%
{results['correct']} / {results['total']} correct
⏱️ Completed in {em}m {es}s
{results['feedback']}
""", unsafe_allow_html=True)
save_progress(st.session_state.progress, score=score, topic=quiz["topic"])
# Gamification: award XP for quiz
gami = update_streak(st.session_state.gami)
xp_for_quiz = get_xp_for_quiz(score)
gami, xp_amt, lvl_up = award_xp(gami, xp_for_quiz)
gami = record_quiz(gami, score, len(set(st.session_state.progress.get("topics_studied", []))))
topics_count = len(set(st.session_state.progress.get("topics_studied", [])))
quizzes_count = gami.get("total_quizzes", 0)
new_b = check_and_award_badges(gami, {
"score": score, "topics_count": topics_count,
"quizzes_count": quizzes_count, "event": "quiz",
})
save_gamification(gami)
st.session_state.gami = gami
if new_b:
st.session_state.new_badges = new_b
if lvl_up:
st.session_state.level_up_msg = lvl_up
# Show XP toast
st.markdown(f"""
⚡
+{xp_for_quiz} XP earned!
Total: {gami.get("xp",0)} XP · {get_level(gami.get("xp",0))[0]}
""", unsafe_allow_html=True)
# Show new badges
if st.session_state.new_badges:
for bk in st.session_state.new_badges:
b = BADGES.get(bk, {})
st.markdown(f"""
{b.get("icon","🏅")}
Badge Unlocked: {b.get("name","")}
{b.get("desc","")}
""", unsafe_allow_html=True)
st.session_state.new_badges = []
if lvl_up:
st.balloons()
st.success(f"🚀 Level Up! You reached **{lvl_up}**!")
st.session_state.level_up_msg = None
if score < 60:
st.warning(f"📌 Score below 60%. We recommend revisiting **{quiz['topic']}**.")
# Score meter
col_meter = st.columns([1, 2, 1])[1]
with col_meter:
level_label = "Excellent 🌟" if score == 100 else "Great 🎉" if score >= 80 else "Good 👍" if score >= 60 else "Fair 📖" if score >= 40 else "Keep Going 💪"
st.markdown(f"{level_label}
", unsafe_allow_html=True)
st.progress(score / 100)
st.markdown("### 📋 Answer Review")
for i, q in enumerate(quiz["questions"]):
correct = results["details"][i]["correct"]
user_ans = st.session_state.answers.get(i, "")
color = "#10b981" if correct else "#ef4444"
icon = "✅" if correct else "❌"
st.markdown(f"""
{icon} Q{i+1}: {q['question']}
Your answer: {user_ans if user_ans else "No answer"}
Correct: {results["details"][i]["correct_answer"]}
{f'
{results["details"][i]["explanation"]}
' if results["details"][i].get("explanation") else ""}
""", unsafe_allow_html=True)
st.markdown("")
c1, c2, c3 = st.columns(3)
with c1:
if st.button("🔁 Retake Quiz", use_container_width=True):
st.session_state.submitted = False
st.session_state.answers = {}
st.session_state.quiz_start_time = time.time()
st.rerun()
with c2:
if st.button("📚 Study This Topic", use_container_width=True):
st.session_state.page = "study"
st.session_state.submitted = False
st.rerun()
with c3:
if st.button("🃏 Flashcard Revision", use_container_width=True):
st.session_state.fc_topic = quiz["topic"]
st.session_state.fc_level = quiz["difficulty"]
st.session_state.page = "flashcards"
st.session_state.submitted = False
st.rerun()
# PDF Export for quiz results
st.markdown("---")
if st.button("⬇️ Download Quiz Results PDF", use_container_width=True):
with st.spinner("Generating PDF…"):
pdf_bytes = export_quiz_results_pdf(quiz, results, st.session_state.answers)
st.download_button(
label="📥 Click to Download Results PDF",
data=pdf_bytes,
file_name=f"LearnCraft_{quiz['topic'].replace(' ','_')}_Results.pdf",
mime="application/pdf",
use_container_width=True,
)
# ══════════════════════ PROGRESS ════════════════════════════════════════════
elif page == "progress":
st.markdown("## 📊 My Learning Progress")
progress = st.session_state.progress
scores = progress.get("scores", [])
sessions = progress.get("sessions", [])
c1, c2, c3, c4 = st.columns(4)
topics_list = list(set(progress.get("topics_studied", [])))
avg = round(sum(scores)/len(scores), 1) if scores else 0
c1.metric("📚 Topics Studied", len(topics_list))
c2.metric("🏆 Best Score", f"{progress.get('best_score', 0)}%")
c3.metric("📈 Avg Score", f"{avg}%")
c4.metric("🎯 Total Quizzes", len(scores))
st.markdown("---")
if sessions:
st.markdown("### 📈 Score History")
df = pd.DataFrame(sessions)
df.index = range(1, len(df) + 1)
df.index.name = "Quiz #"
st.line_chart(df[["score"]].rename(columns={"score": "Score (%)"}), color="#6c47ff")
topic_scores = progress.get("topic_scores", {})
if topic_scores:
st.markdown("### 🏅 Best Score Per Topic")
ts_df = pd.DataFrame([
{"Topic": t, "Best Score (%)": s, "Status": "✅ Passing" if s >= 60 else "⚠️ Needs Review"}
for t, s in sorted(topic_scores.items(), key=lambda x: -x[1])
])
st.dataframe(ts_df, use_container_width=True, hide_index=True)
weak = get_weak_topics(progress)
if weak:
st.markdown("### ⚠️ Topics to Improve")
for t in weak:
st.markdown(f"""
⚠️ {t} — below 60%
""", unsafe_allow_html=True)
if topics_list:
st.markdown("### 🗂️ Topics Covered")
cols = st.columns(3)
for i, t in enumerate(topics_list):
with cols[i % 3]:
best = topic_scores.get(t, 0)
color = "#10b981" if best >= 80 else "#f97316" if best >= 60 else "#ef4444"
st.markdown(f"""
""", unsafe_allow_html=True)
if not topics_list and not scores:
st.markdown("""
🌱
No activity yet. Start learning to see your progress!
""", unsafe_allow_html=True)
if st.button("📚 Start Learning Now", use_container_width=True):
st.session_state.page = "study"; st.rerun()
st.markdown("")
if st.button("🗑️ Reset All Progress", type="secondary"):
st.session_state.progress = {"topics_studied": [], "scores": [], "best_score": 0, "sessions": [], "topic_scores": {}}
save_progress(st.session_state.progress)
st.success("Progress reset.")
st.rerun()
# ══════════════════════ NOTES ═══════════════════════════════════════════════
elif page == "notes":
st.markdown("## 📝 My Notes")
st.markdown("Notes you've saved while studying.
", unsafe_allow_html=True)
notes = load_notes()
if not notes:
st.markdown("""
📭
No notes yet. Save notes from the Study page!
""", unsafe_allow_html=True)
if st.button("📚 Go to Study", use_container_width=True):
st.session_state.page = "study"; st.rerun()
else:
# Search filter
search = st.text_input("🔍 Search notes", placeholder="Filter by keyword…")
filtered = [n for n in reversed(notes) if not search or search.lower() in n.get("note","").lower() or search.lower() in n.get("topic","").lower()]
st.markdown(f"**{len(filtered)} note(s)**")
for i, note in enumerate(filtered):
actual_index = notes.index(note) if note in notes else -1
col_note, col_del = st.columns([11, 1])
with col_note:
st.markdown(f"""
📌 {note.get("topic","Unknown")} · {note.get("date","")}
{note.get("note","")}
""", unsafe_allow_html=True)
with col_del:
if st.button("🗑️", key=f"del_{i}", help="Delete note"):
if actual_index >= 0:
delete_note(actual_index)
st.rerun()
# ══════════════════════ AI TUTOR ═════════════════════════════════════════════
elif page == "tutor":
st.markdown("## 🤖 AI Tutor")
st.markdown("Ask anything about any topic. Your tutor remembers the full conversation.
", unsafe_allow_html=True)
# Topic context selector
col_t, col_c = st.columns([3, 1])
with col_t:
tutor_topic = st.text_input(
"📌 Topic context (optional)",
value=st.session_state.tutor_topic,
placeholder="e.g. Quantum Mechanics — helps the tutor stay focused",
)
st.session_state.tutor_topic = tutor_topic
with col_c:
st.markdown("", unsafe_allow_html=True)
if st.button("🗑️ Clear Chat", use_container_width=True):
st.session_state.tutor_messages = []
st.rerun()
st.markdown("---")
# Render conversation history
msgs = st.session_state.tutor_messages
if not msgs:
st.markdown("""
🤖
Hi! I'm your LearnCraft Tutor.
Ask me anything about your topic — concepts, examples, quick tests, or explanations.
""", unsafe_allow_html=True)
# Quick-start prompts
st.markdown("#### 💡 Try asking:")
prompts = [
"Explain this topic like I'm 10 years old",
"Give me 3 real-world examples",
"What are the most common mistakes beginners make?",
"Quiz me with one question",
]
p_cols = st.columns(2)
for i, prompt in enumerate(prompts):
with p_cols[i % 2]:
if st.button(f'"{prompt}"', key=f"prompt_{i}", use_container_width=True):
full_prompt = prompt + (f" about {tutor_topic}" if tutor_topic else "")
st.session_state.tutor_messages.append({"role": "user", "content": full_prompt})
with st.spinner("Thinking…"):
reply = get_tutor_reply(st.session_state.tutor_messages, tutor_topic)
st.session_state.tutor_messages.append({"role": "assistant", "content": reply})
st.rerun()
else:
for msg in msgs:
if msg["role"] == "user":
st.markdown(f"""
You
{msg['content']}
""", unsafe_allow_html=True)
else:
st.markdown(f"""
🤖 Tutor
{msg['content']}
""", unsafe_allow_html=True)
# Input box
st.markdown("", unsafe_allow_html=True)
with st.form("chat_form", clear_on_submit=True):
user_input = st.text_input(
"Your question",
placeholder="Ask your tutor anything…",
label_visibility="collapsed",
)
submitted = st.form_submit_button("Send ➤", use_container_width=True)
if submitted and user_input.strip():
st.session_state.tutor_messages.append({"role": "user", "content": user_input.strip()})
with st.spinner("Tutor is thinking…"):
reply = get_tutor_reply(st.session_state.tutor_messages, tutor_topic)
st.session_state.tutor_messages.append({"role": "assistant", "content": reply})
st.rerun()
# ══════════════════════ ACHIEVEMENTS ════════════════════════════════════════
elif page == "achievements":
gami = st.session_state.gami
xp = gami.get("xp", 0)
streak = gami.get("streak", 0)
earned = set(gami.get("badges", []))
level_name, next_level_name, xp_to_next, level_pct = get_level(xp)
st.markdown("## 🏅 Achievements")
st.markdown("Your XP, level, streak and badges.
", unsafe_allow_html=True)
# XP / Level hero card
st.markdown(f"""
Current Level
{level_name}
{xp} XP total
{"
" + str(xp_to_next) + " XP to " + str(next_level_name) + "
" if next_level_name else "
Maximum level reached! 🚀
"}
""", unsafe_allow_html=True)
# Stats row
total_quizzes = gami.get("total_quizzes", 0)
badges_count = len(earned)
s1, s2, s3, s4 = st.columns(4)
for col, icon, val, label in [
(s1, "🔥", f"{streak} days", "Current Streak"),
(s2, "⚡", f"{xp} XP", "Total XP"),
(s3, "🧩", str(total_quizzes), "Quizzes Done"),
(s4, "🏅", f"{badges_count}/{len(BADGES)}", "Badges Earned"),
]:
with col:
st.markdown(f"""
""", unsafe_allow_html=True)
st.markdown("---")
st.markdown("### 🏅 Badge Collection")
# Filter tabs
filter_tab1, filter_tab2 = st.tabs(["All Badges", "Earned Only"])
def render_badges(badge_list):
cols = st.columns(4)
for i, (key, badge) in enumerate(badge_list):
is_earned = key in earned
card_class = "badge-card earned" if is_earned else "badge-card locked"
lock_icon = badge["icon"] if is_earned else "🔒"
opacity = "1" if is_earned else "0.5"
with cols[i % 4]:
st.markdown(f"""
{lock_icon}
{badge["name"]}
{badge["desc"]}
{"
✓ Earned
" if is_earned else ""}
""", unsafe_allow_html=True)
with filter_tab1:
render_badges(list(BADGES.items()))
with filter_tab2:
earned_list = [(k, v) for k, v in BADGES.items() if k in earned]
if earned_list:
render_badges(earned_list)
else:
st.markdown("""
🔒
No badges yet — complete quizzes and study sessions to earn them!
""", unsafe_allow_html=True)
# How to earn XP guide
st.markdown("---")
st.markdown("### ⚡ How to Earn XP")
xp_guide = [
("📚", "Study session", "+10 XP"),
("🃏", "Flashcard deck", "+10 XP"),
("🧩", "Complete a quiz", "+20 XP"),
("🎯", "Score ≥ 60%", "+15 XP"),
("🎉", "Score ≥ 80%", "+30 XP"),
("💯", "Perfect score 100%","+50 XP"),
]
xp_cols = st.columns(3)
for i, (icon, action, xp_val) in enumerate(xp_guide):
with xp_cols[i % 3]:
st.markdown(f"""
""", unsafe_allow_html=True)