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"""
🔥{streak}
Streak
{topics_count}
Topics
🏅{badges_earned}
Badges
{best_score}%
Best
""", 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"""
{icon}

{title}

{desc}

""", 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"""
QUESTION
{card['front']}
ANSWER
{card['back']}
""", 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"""
{i+1}. {c['front']}
""", 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"""
📌 {t}
{best}%
""", 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"""
{icon}
{val}
{label}
""", 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"""
{icon} {action}
{xp_val}
""", unsafe_allow_html=True)