lanna_lalala;- commited on
Commit ·
fdf0e7a
1
Parent(s): 618422f
miniquiz try
Browse files- phase/Student_view/lesson.py +2 -140
- phase/Student_view/mini_quiz.py +28 -14
- utils/api.py +1 -1
phase/Student_view/lesson.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
from typing import List, Dict, Any, Optional, Tuple
|
| 3 |
import re
|
| 4 |
-
import datetime
|
| 5 |
import os
|
| 6 |
from utils import db as dbapi
|
| 7 |
from utils import api as backend_api # unified backend client
|
|
@@ -28,10 +27,7 @@ _SS_DEFAULTS = {
|
|
| 28 |
"topic_idx": 0, # 0-based within module
|
| 29 |
"mode": "catalog", # catalog | lesson | quiz | results
|
| 30 |
"topics_cache": {}, # {(level, module_id): [(title, text), ...]}
|
| 31 |
-
|
| 32 |
-
# "quiz_answers": {}, # q_index -> "A"|"B"|"C"|"D"
|
| 33 |
-
# "quiz_result": None, # backend result dict
|
| 34 |
-
# "chatbot_feedback": None, # str
|
| 35 |
}
|
| 36 |
|
| 37 |
def _ensure_state():
|
|
@@ -219,90 +215,6 @@ def _extract_takeaways(text: str, max_items: int = 5) -> List[str]:
|
|
| 219 |
sents = re.split(r"(?<=[.!?])\s+", text.strip())
|
| 220 |
return [s for s in sents if len(s) > 20][:min(max_items, 3)]
|
| 221 |
|
| 222 |
-
# def _start_quiz(level: str, module_id: int) -> Optional[List[Dict[str, Any]]]:
|
| 223 |
-
# """Ask backend to generate a 5-question mini quiz for this module."""
|
| 224 |
-
# module_conf = next(m for m in MODULES_META[level] if m["id"] == module_id)
|
| 225 |
-
# try:
|
| 226 |
-
# quiz = backend_api.generate_quiz(
|
| 227 |
-
# lesson_id=module_id,
|
| 228 |
-
# level_slug=level,
|
| 229 |
-
# lesson_title=module_conf["title"],
|
| 230 |
-
# )
|
| 231 |
-
# if isinstance(quiz, list) and quiz:
|
| 232 |
-
# return quiz
|
| 233 |
-
# return None
|
| 234 |
-
# except Exception as e:
|
| 235 |
-
# st.error(f"Could not generate quiz: {e}")
|
| 236 |
-
# return None
|
| 237 |
-
|
| 238 |
-
# def _submit_quiz(level: str, module_id: int, original_quiz: List[Dict[str, Any]], answers_map: Dict[int, str]) -> Optional[Dict[str, Any]]:
|
| 239 |
-
# """Submit answers and get score + tutor feedback."""
|
| 240 |
-
# user_answers = []
|
| 241 |
-
# for i, q in enumerate(original_quiz):
|
| 242 |
-
# user_answers.append({
|
| 243 |
-
# "question": q.get("question", f"Q{i+1}"),
|
| 244 |
-
# "answer": answers_map.get(i, ""), # "A".."D"
|
| 245 |
-
# })
|
| 246 |
-
|
| 247 |
-
# # student_id is required by utils/api.submit_quiz
|
| 248 |
-
# student_id = int(((st.session_state.get("user") or {}).get("user_id") or 0))
|
| 249 |
-
|
| 250 |
-
# try:
|
| 251 |
-
# result = backend_api.submit_quiz(
|
| 252 |
-
# student_id=student_id,
|
| 253 |
-
# lesson_id=module_id,
|
| 254 |
-
# level_slug=level,
|
| 255 |
-
# user_answers=user_answers,
|
| 256 |
-
# original_quiz=original_quiz,
|
| 257 |
-
# )
|
| 258 |
-
# return result
|
| 259 |
-
# except Exception as e:
|
| 260 |
-
# st.error(f"Could not submit quiz: {e}")
|
| 261 |
-
# return None
|
| 262 |
-
|
| 263 |
-
# def _send_quiz_summary_to_chatbot(result: Dict[str, Any]):
|
| 264 |
-
# """
|
| 265 |
-
# Send a concise, actionable summary of the quiz outcome to the chatbot,
|
| 266 |
-
# then navigate to the Chatbot page with the conversation pre-seeded.
|
| 267 |
-
# """
|
| 268 |
-
# level = st.session_state.level
|
| 269 |
-
# module_id = st.session_state.module_id
|
| 270 |
-
# mod = next(m for m in MODULES_META[level] if m["id"] == module_id)
|
| 271 |
-
|
| 272 |
-
# score = result.get("score", {})
|
| 273 |
-
# correct = int(score.get("correct", 0))
|
| 274 |
-
# total = int(score.get("total", 0))
|
| 275 |
-
# feedback = (result.get("feedback") or st.session_state.get("chatbot_feedback") or "").strip()
|
| 276 |
-
|
| 277 |
-
# user_prompt = (
|
| 278 |
-
# f"I just finished the quiz for '{mod['title']}' (module {module_id}) "
|
| 279 |
-
# f"and scored {correct}/{total}. Please give me 2–3 targeted tips and 1 tiny action "
|
| 280 |
-
# f"to improve before the next lesson. If there were wrong answers, explain them simply.\n\n"
|
| 281 |
-
# f"Context from grader:\n{feedback}"
|
| 282 |
-
# )
|
| 283 |
-
|
| 284 |
-
# try:
|
| 285 |
-
# # Call FastAPI /chat via utils.api.chat_ai
|
| 286 |
-
# bot_reply = (backend_api.chat_ai(
|
| 287 |
-
# query=user_prompt,
|
| 288 |
-
# lesson_id=module_id,
|
| 289 |
-
# level_slug=level,
|
| 290 |
-
# history=[]
|
| 291 |
-
# ) or "").strip()
|
| 292 |
-
# except Exception:
|
| 293 |
-
# bot_reply = f"(Chatbot unavailable) Based on your result: {feedback or 'Nice work!'}"
|
| 294 |
-
|
| 295 |
-
# # Seed Chatbot page
|
| 296 |
-
# msgs = st.session_state.get("messages") or [{
|
| 297 |
-
# "id": "1",
|
| 298 |
-
# "text": "Hi! I'm your AI Financial Tutor. What would you like to learn today?",
|
| 299 |
-
# "sender": "assistant",
|
| 300 |
-
# "timestamp": datetime.datetime.now(),
|
| 301 |
-
# }]
|
| 302 |
-
# msgs.append({"text": user_prompt, "sender": "user", "timestamp": datetime.datetime.now()})
|
| 303 |
-
# msgs.append({"text": bot_reply, "sender": "assistant", "timestamp": datetime.datetime.now()})
|
| 304 |
-
# st.session_state.messages = msgs
|
| 305 |
-
# st.session_state.current_page = "Chatbot"
|
| 306 |
|
| 307 |
def _fallback_text(title: str, module_id: int, topic_ordinal: int) -> str:
|
| 308 |
"""
|
|
@@ -387,8 +299,6 @@ def _get_topics(level: str, module_id: int) -> List[Tuple[str, str]]:
|
|
| 387 |
st.session_state.topics_cache[cache_key] = out
|
| 388 |
return out
|
| 389 |
|
| 390 |
-
# def _letter_for(i: int) -> str:
|
| 391 |
-
# return chr(ord("A") + i)
|
| 392 |
|
| 393 |
def _render_lesson():
|
| 394 |
ensure_quiz_state() # make sure quiz keys exist
|
|
@@ -483,51 +393,6 @@ def _render_lesson():
|
|
| 483 |
st.button(label, key=f"jump_{i}", on_click=lambda j=i: st.session_state.update({"topic_idx": j}) or st.rerun())
|
| 484 |
|
| 485 |
|
| 486 |
-
# def _render_quiz():
|
| 487 |
-
# quiz: List[Dict[str, Any]] = st.session_state.quiz_data or []
|
| 488 |
-
# if not quiz:
|
| 489 |
-
# st.session_state.mode = "lesson"
|
| 490 |
-
# st.rerun()
|
| 491 |
-
|
| 492 |
-
# st.markdown("### Lesson Quiz")
|
| 493 |
-
|
| 494 |
-
# # Render each question (single page)
|
| 495 |
-
# for q_idx, q in enumerate(quiz):
|
| 496 |
-
# st.markdown(f"**Q{q_idx+1}. {q.get('question','').strip()}**")
|
| 497 |
-
# opts = q.get("options") or []
|
| 498 |
-
|
| 499 |
-
# def _on_select():
|
| 500 |
-
# sel = st.session_state[f"ans_{q_idx}"] # "A. option text"
|
| 501 |
-
# letter = sel.split(".", 1)[0] if isinstance(sel, str) else ""
|
| 502 |
-
# st.session_state.quiz_answers[q_idx] = letter
|
| 503 |
-
|
| 504 |
-
# labels = [f"{_letter_for(i)}. {opt}" for i, opt in enumerate(opts)]
|
| 505 |
-
# saved_letter = st.session_state.quiz_answers.get(q_idx)
|
| 506 |
-
# pre_idx = next((i for i, l in enumerate(labels) if saved_letter and l.startswith(f"{saved_letter}.")), None)
|
| 507 |
-
|
| 508 |
-
# st.radio(
|
| 509 |
-
# "",
|
| 510 |
-
# labels,
|
| 511 |
-
# index=pre_idx,
|
| 512 |
-
# key=f"ans_{q_idx}",
|
| 513 |
-
# on_change=_on_select,
|
| 514 |
-
# )
|
| 515 |
-
# st.divider()
|
| 516 |
-
|
| 517 |
-
# all_answered = len(st.session_state.quiz_answers) == len(quiz)
|
| 518 |
-
# if st.button("Submit Quiz", disabled=not all_answered):
|
| 519 |
-
# with st.spinner("Grading…"):
|
| 520 |
-
# result = _submit_quiz(
|
| 521 |
-
# st.session_state.level,
|
| 522 |
-
# st.session_state.module_id,
|
| 523 |
-
# quiz,
|
| 524 |
-
# st.session_state.quiz_answers,
|
| 525 |
-
# )
|
| 526 |
-
# if result:
|
| 527 |
-
# st.session_state.quiz_result = result
|
| 528 |
-
# st.session_state.chatbot_feedback = result.get("feedback")
|
| 529 |
-
# _send_quiz_summary_to_chatbot(result)
|
| 530 |
-
# st.rerun()
|
| 531 |
|
| 532 |
def _render_results():
|
| 533 |
result = st.session_state.quiz_result or {}
|
|
@@ -557,10 +422,7 @@ def _render_results():
|
|
| 557 |
|
| 558 |
# Let mini_quiz handle the full results UI (review, feedback, nav)
|
| 559 |
quiz_results(planned_topics=planned)
|
| 560 |
-
|
| 561 |
-
# quiz_index = [t.strip().lower() for t in planned].index("quiz")
|
| 562 |
-
# except ValueError:
|
| 563 |
-
# quiz_index = None
|
| 564 |
|
| 565 |
c1, c2, c3 = st.columns([1, 1, 1])
|
| 566 |
with c1:
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
from typing import List, Dict, Any, Optional, Tuple
|
| 3 |
import re
|
|
|
|
| 4 |
import os
|
| 5 |
from utils import db as dbapi
|
| 6 |
from utils import api as backend_api # unified backend client
|
|
|
|
| 27 |
"topic_idx": 0, # 0-based within module
|
| 28 |
"mode": "catalog", # catalog | lesson | quiz | results
|
| 29 |
"topics_cache": {}, # {(level, module_id): [(title, text), ...]}
|
| 30 |
+
|
|
|
|
|
|
|
|
|
|
| 31 |
}
|
| 32 |
|
| 33 |
def _ensure_state():
|
|
|
|
| 215 |
sents = re.split(r"(?<=[.!?])\s+", text.strip())
|
| 216 |
return [s for s in sents if len(s) > 20][:min(max_items, 3)]
|
| 217 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
|
| 219 |
def _fallback_text(title: str, module_id: int, topic_ordinal: int) -> str:
|
| 220 |
"""
|
|
|
|
| 299 |
st.session_state.topics_cache[cache_key] = out
|
| 300 |
return out
|
| 301 |
|
|
|
|
|
|
|
| 302 |
|
| 303 |
def _render_lesson():
|
| 304 |
ensure_quiz_state() # make sure quiz keys exist
|
|
|
|
| 393 |
st.button(label, key=f"jump_{i}", on_click=lambda j=i: st.session_state.update({"topic_idx": j}) or st.rerun())
|
| 394 |
|
| 395 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
|
| 397 |
def _render_results():
|
| 398 |
result = st.session_state.quiz_result or {}
|
|
|
|
| 422 |
|
| 423 |
# Let mini_quiz handle the full results UI (review, feedback, nav)
|
| 424 |
quiz_results(planned_topics=planned)
|
| 425 |
+
|
|
|
|
|
|
|
|
|
|
| 426 |
|
| 427 |
c1, c2, c3 = st.columns([1, 1, 1])
|
| 428 |
with c1:
|
phase/Student_view/mini_quiz.py
CHANGED
|
@@ -29,11 +29,11 @@ def ensure_quiz_state():
|
|
| 29 |
# Backend calls (isolated here)
|
| 30 |
# ---------------------------------------------
|
| 31 |
|
|
|
|
| 32 |
def start_quiz(level: str, module_id: int, lesson_title: str) -> bool:
|
| 33 |
-
"""Generate and stage a quiz for the given module. Returns True on success."""
|
| 34 |
ensure_quiz_state()
|
| 35 |
try:
|
| 36 |
-
|
| 37 |
lesson_id=module_id,
|
| 38 |
level_slug=level,
|
| 39 |
lesson_title=lesson_title,
|
|
@@ -42,24 +42,38 @@ def start_quiz(level: str, module_id: int, lesson_title: str) -> bool:
|
|
| 42 |
st.error(f"Could not generate quiz: {e}")
|
| 43 |
return False
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
st.
|
| 49 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
-
st.
|
| 52 |
-
|
|
|
|
|
|
|
| 53 |
|
| 54 |
|
| 55 |
def submit_quiz(level: str, module_id: int, original_quiz: List[Dict[str, Any]], answers_map: Dict[int, str]) -> Optional[Dict[str, Any]]:
|
| 56 |
"""Submit answers and return the grading result dict."""
|
|
|
|
| 57 |
user_answers = []
|
| 58 |
-
for
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
})
|
| 63 |
|
| 64 |
# student_id is required by utils.api.submit_quiz
|
| 65 |
student_id = int(((st.session_state.get("user") or {}).get("user_id") or 0))
|
|
|
|
| 29 |
# Backend calls (isolated here)
|
| 30 |
# ---------------------------------------------
|
| 31 |
|
| 32 |
+
# --- in start_quiz() ---
|
| 33 |
def start_quiz(level: str, module_id: int, lesson_title: str) -> bool:
|
|
|
|
| 34 |
ensure_quiz_state()
|
| 35 |
try:
|
| 36 |
+
resp = backend_api.generate_quiz(
|
| 37 |
lesson_id=module_id,
|
| 38 |
level_slug=level,
|
| 39 |
lesson_title=lesson_title,
|
|
|
|
| 42 |
st.error(f"Could not generate quiz: {e}")
|
| 43 |
return False
|
| 44 |
|
| 45 |
+
# Accept both the old (list) and new (dict with 'items') shapes
|
| 46 |
+
items = resp.get("items") if isinstance(resp, dict) else resp
|
| 47 |
+
if not isinstance(items, list) or not items:
|
| 48 |
+
st.error("Quiz could not be generated. Please try again.")
|
| 49 |
+
return False
|
| 50 |
+
|
| 51 |
+
# Normalize: ensure id + answer_key are present
|
| 52 |
+
normalized = []
|
| 53 |
+
for i, q in enumerate(items, start=1):
|
| 54 |
+
qid = q.get("id") or q.get("qid") or q.get("position") or f"q{i}"
|
| 55 |
+
answer_key = q.get("answer_key") or q.get("answer") # backend may send either
|
| 56 |
+
normalized.append({
|
| 57 |
+
"id": qid,
|
| 58 |
+
"question": (q.get("question") or "").strip(),
|
| 59 |
+
"options": q.get("options") or [],
|
| 60 |
+
"answer_key": answer_key, # keep for grading payload
|
| 61 |
+
})
|
| 62 |
|
| 63 |
+
st.session_state.quiz_data = normalized
|
| 64 |
+
st.session_state.quiz_answers = {}
|
| 65 |
+
st.session_state.mode = "quiz"
|
| 66 |
+
return True
|
| 67 |
|
| 68 |
|
| 69 |
def submit_quiz(level: str, module_id: int, original_quiz: List[Dict[str, Any]], answers_map: Dict[int, str]) -> Optional[Dict[str, Any]]:
|
| 70 |
"""Submit answers and return the grading result dict."""
|
| 71 |
+
# --- in submit_quiz() ---
|
| 72 |
user_answers = []
|
| 73 |
+
for q in original_quiz:
|
| 74 |
+
qid = q.get("id") or q.get("question") # fallback
|
| 75 |
+
user_answers.append({"id": qid, "answer": answers_map.get(original_quiz.index(q), "")})
|
| 76 |
+
|
|
|
|
| 77 |
|
| 78 |
# student_id is required by utils.api.submit_quiz
|
| 79 |
student_id = int(((st.session_state.get("user") or {}).get("user_id") or 0))
|
utils/api.py
CHANGED
|
@@ -27,7 +27,7 @@ _session.mount("https://", HTTPAdapter(max_retries=retry))
|
|
| 27 |
_session.mount("http://", HTTPAdapter(max_retries=retry))
|
| 28 |
_session.headers.update({
|
| 29 |
"Accept": "application/json, */*;q=0.1",
|
| 30 |
-
"User-Agent": "
|
| 31 |
})
|
| 32 |
if TOKEN:
|
| 33 |
_session.headers["Authorization"] = f"Bearer {TOKEN}"
|
|
|
|
| 27 |
_session.mount("http://", HTTPAdapter(max_retries=retry))
|
| 28 |
_session.headers.update({
|
| 29 |
"Accept": "application/json, */*;q=0.1",
|
| 30 |
+
"User-Agent": "FInFront/1.0 (+spaces)",
|
| 31 |
})
|
| 32 |
if TOKEN:
|
| 33 |
_session.headers["Authorization"] = f"Bearer {TOKEN}"
|