Download phase/Student_view/mini_quiz.py from FinEDProj/FInFront: direct link, hf CLI and curl.
- Browser
- Download file 8.78 kB
-
https://huggingface.co/spaces/FinEDProj/FInFront/resolve/main/phase/Student_view/mini_quiz.py
- Command line
-
hf download hf://spaces/FinEDProj/FInFront/phase/Student_view/mini_quiz.py
-
curl -L -o mini_quiz.py https://huggingface.co/spaces/FinEDProj/FInFront/resolve/main/phase/Student_view/mini_quiz.py
8.78 kB
| # phase/Student_view/mini_quiz.py | |
| import datetime | |
| from typing import List, Dict, Any, Optional | |
| import streamlit as st | |
| # Reuse the unified backend client | |
| from utils import api as backend_api | |
| # --------------------------------------------- | |
| # Public quiz-state helpers | |
| # --------------------------------------------- | |
| QUIZ_SS_DEFAULTS = { | |
| "quiz_data": None, # original quiz payload (list[dict]) | |
| "quiz_answers": {}, # q_index -> "A"|"B"|"C"|"D" | |
| "quiz_result": None, # backend result dict | |
| "chatbot_feedback": None, # str | |
| "_auto_quiz_started": False, # helper to avoid double-start | |
| } | |
| def ensure_quiz_state(): | |
| """Ensure quiz-related keys exist in st.session_state.""" | |
| for k, v in QUIZ_SS_DEFAULTS.items(): | |
| if k not in st.session_state: | |
| st.session_state[k] = v | |
| # --------------------------------------------- | |
| # Backend calls (isolated here) | |
| # --------------------------------------------- | |
| # --- in start_quiz() --- | |
| def start_quiz(level: str, module_id: int, lesson_title: str) -> bool: | |
| ensure_quiz_state() | |
| try: | |
| resp = backend_api.generate_quiz( | |
| lesson_id=module_id, | |
| level_slug=level, | |
| lesson_title=lesson_title, | |
| ) | |
| except Exception as e: | |
| st.error(f"Could not generate quiz: {e}") | |
| return False | |
| # Accept both the old (list) and new (dict with 'items') shapes | |
| items = resp.get("items") if isinstance(resp, dict) else resp | |
| if not isinstance(items, list) or not items: | |
| st.error("Quiz could not be generated. Please try again.") | |
| return False | |
| # Normalize: ensure id + answer_key are present | |
| normalized = [] | |
| for i, q in enumerate(items, start=1): | |
| qid = q.get("id") or q.get("qid") or q.get("position") or f"q{i}" | |
| answer_key = q.get("answer_key") or q.get("answer") # backend may send either | |
| normalized.append({ | |
| "id": qid, | |
| "question": (q.get("question") or "").strip(), | |
| "options": q.get("options") or [], | |
| "answer_key": answer_key, # keep for grading payload | |
| }) | |
| st.session_state.quiz_data = normalized | |
| st.session_state.quiz_answers = {} | |
| st.session_state.mode = "quiz" | |
| return True | |
| def submit_quiz(level: str, module_id: int, original_quiz: List[Dict[str, Any]], answers_map: Dict[int, str]) -> Optional[Dict[str, Any]]: | |
| """Submit answers and return the grading result dict.""" | |
| # --- in submit_quiz() --- | |
| user_answers = [] | |
| for q in original_quiz: | |
| qid = q.get("id") or q.get("question") # fallback | |
| user_answers.append({"id": qid, "answer": answers_map.get(original_quiz.index(q), "")}) | |
| # student_id is required by utils.api.submit_quiz | |
| student_id = int(((st.session_state.get("user") or {}).get("user_id") or 0)) | |
| try: | |
| result = backend_api.submit_quiz( | |
| student_id=student_id, | |
| lesson_id=module_id, | |
| level_slug=level, | |
| user_answers=user_answers, | |
| original_quiz=original_quiz, | |
| ) | |
| return result | |
| except Exception as e: | |
| st.error(f"Could not submit quiz: {e}") | |
| return None | |
| def send_quiz_summary_to_chatbot(level: str, module_id: int, lesson_title: str, result: Dict[str, Any]): | |
| """Send a concise summary to the chatbot and navigate there.""" | |
| score = result.get("score", {}) | |
| correct = int(score.get("correct", 0)) | |
| total = int(score.get("total", 0)) | |
| feedback = (result.get("feedback") or st.session_state.get("chatbot_feedback") or "").strip() | |
| user_prompt = ( | |
| f"I just finished the quiz for '{lesson_title}' (module {module_id}) " | |
| f"and scored {correct}/{total}. Please give me 2–3 targeted tips and 1 tiny action " | |
| f"to improve before the next lesson. If there were wrong answers, explain them simply.\n\n" | |
| f"Context from grader:\n{feedback}" | |
| ) | |
| try: | |
| bot_reply = (backend_api.chat_ai( | |
| query=user_prompt, | |
| lesson_id=module_id, | |
| level_slug=level, | |
| history=[], | |
| ) or "").strip() | |
| except Exception: | |
| bot_reply = f"(Chatbot unavailable) Based on your result: {feedback or 'Nice work!'}" | |
| # Seed Chatbot page | |
| msgs = st.session_state.get("messages") or [{ | |
| "id": "1", | |
| "text": "Hi! I'm your AI Financial Tutor. What would you like to learn today?", | |
| "sender": "assistant", | |
| "timestamp": datetime.datetime.now(), | |
| }] | |
| msgs.append({"text": user_prompt, "sender": "user", "timestamp": datetime.datetime.now()}) | |
| msgs.append({"text": bot_reply, "sender": "assistant", "timestamp": datetime.datetime.now()}) | |
| st.session_state.messages = msgs | |
| st.session_state.current_page = "Chatbot" | |
| # --------------------------------------------- | |
| # UI helpers | |
| # --------------------------------------------- | |
| def _letter_for(i: int) -> str: | |
| return chr(ord("A") + i) | |
| def render_quiz(lesson_title: Optional[str] = None): | |
| """Render the quiz UI (single page). Expects st.session_state.level/module_id to be set.""" | |
| ensure_quiz_state() | |
| quiz: List[Dict[str, Any]] = st.session_state.quiz_data or [] | |
| if not quiz: | |
| # No staged quiz — bounce back to lesson | |
| st.session_state.mode = "lesson" | |
| st.rerun() | |
| st.markdown("### Lesson Quiz") | |
| # Render each question | |
| for q_idx, q in enumerate(quiz): | |
| st.markdown(f"**Q{q_idx+1}. {q.get('question','').strip()}**") | |
| opts = q.get("options") or [] | |
| def _on_select(): | |
| sel = st.session_state[f"ans_{q_idx}"] # e.g. "A. option text" | |
| letter = sel.split(".", 1)[0] if isinstance(sel, str) else "" | |
| st.session_state.quiz_answers[q_idx] = letter | |
| labels = [f"{_letter_for(i)}. {opt}" for i, opt in enumerate(opts)] | |
| saved_letter = st.session_state.quiz_answers.get(q_idx) | |
| pre_idx = next((i for i, l in enumerate(labels) if saved_letter and l.startswith(f"{saved_letter}.")), None) | |
| st.radio( | |
| "", | |
| labels, | |
| index=pre_idx, | |
| key=f"ans_{q_idx}", | |
| on_change=_on_select, | |
| ) | |
| st.divider() | |
| all_answered = len(st.session_state.quiz_answers) == len(quiz) | |
| if st.button("Submit Quiz", disabled=not all_answered): | |
| with st.spinner("Grading…"): | |
| result = submit_quiz( | |
| st.session_state.level, | |
| st.session_state.module_id, | |
| quiz, | |
| st.session_state.quiz_answers, | |
| ) | |
| if result: | |
| st.session_state.quiz_result = result | |
| st.session_state.chatbot_feedback = result.get("feedback") | |
| # Keep current behavior: jump to Chatbot after grading | |
| send_quiz_summary_to_chatbot( | |
| st.session_state.level, | |
| st.session_state.module_id, | |
| lesson_title or "This Lesson", | |
| result, | |
| ) | |
| st.rerun() | |
| def render_results(planned_topics: Optional[List[str]] = None): | |
| """Optional results screen (not used by default flow which jumps to Chatbot).""" | |
| ensure_quiz_state() | |
| result = st.session_state.quiz_result or {} | |
| score = result.get("score", {}) | |
| correct = score.get("correct", 0) | |
| total = score.get("total", 0) | |
| st.success(f"Quiz Complete! You scored {correct} / {total}.") | |
| wrong = result.get("wrong", []) | |
| if wrong: | |
| with st.expander("Review your answers"): | |
| for w in wrong: | |
| st.markdown(f"**{w.get('question','')}**") | |
| st.write(f"Your answer: {w.get('your_answer','')}") | |
| st.write(f"Correct answer: {w.get('correct_answer','')}") | |
| st.divider() | |
| fb = st.session_state.chatbot_feedback | |
| if fb: | |
| st.markdown("#### Tutor Explanation") | |
| st.write(fb) | |
| # Navigation controls | |
| planned_topics = planned_topics or [] | |
| try: | |
| quiz_index = [t.strip().lower() for t in planned_topics].index("quiz") | |
| except ValueError: | |
| quiz_index = None | |
| c1, c2, c3 = st.columns([1, 1, 1]) | |
| with c1: | |
| if st.button("Back to Modules"): | |
| st.session_state.mode = "catalog" | |
| st.session_state.module_id = None | |
| st.rerun() | |
| with c2: | |
| if st.button("Ask the Chatbot →"): | |
| st.session_state.current_page = "Chatbot" | |
| st.session_state.chatbot_prefill = fb | |
| st.rerun() | |
| with c3: | |
| if quiz_index is not None and quiz_index + 1 < len(planned_topics): | |
| if st.button("Continue Lesson →"): | |
| st.session_state.mode = "lesson" | |
| st.session_state.topic_idx = quiz_index + 1 | |
| st.rerun() | |