diff --git a/backend/app/tasks/quiz_tasks.py b/backend/app/tasks/quiz_tasks.py index 3ccd39d..552fec7 100644 --- a/backend/app/tasks/quiz_tasks.py +++ b/backend/app/tasks/quiz_tasks.py @@ -102,6 +102,9 @@ def extract_quiz( _push_step(r, job_id, "text", f"Large section: splitting into {n_chunks} chunks of up to {CHUNK_PAGES} pages each.") all_valid_questions = [] + # A page whose text is missing is a different failure from a page the + # model found nothing in, and the two were reported as one. + pages_without_text = 0 all_skipped = [] # ── Non-standard extraction modes ───────────────────────────────────── @@ -206,7 +209,6 @@ def extract_quiz( extraction_mode = "standard" if extraction_mode == "standard": - # ── STANDARD: existing working extraction (unchanged) ────────────── for chunk_idx, (start_p, end_p) in enumerate(chunks, 1): if r.get(f"extraction:status:{job_id}") == "cancelled": _push_step(r, job_id, "cancelled", "Job cancelled.") @@ -219,7 +221,11 @@ def extract_quiz( document_id=section.document_id, start_page=start_p, end_page=end_p, ) if not chunk_content: - _push_step(r, job_id, "ai", f" No text found for pages {start_p}–{end_p}, skipping.") + pages_without_text += 1 + _push_step(r, job_id, "ai", + f" No stored text for pages {start_p}–{end_p}. The document's text is" + f" read from the search index, not the file, so this usually means it" + f" was never processed or its index was lost.") continue try: chunk_data = ai_service.extract_questions( @@ -246,7 +252,16 @@ def extract_quiz( _push_step(r, job_id, "ai", f"Extraction complete: {len(valid_questions)} valid questions{f', {len(skipped)} skipped' if skipped else ''}.") if not valid_questions: - raise ValueError("No valid questions extracted. The AI could not find questions with correct answers in this page range.") + # Blaming the model for a document that was never indexed sent + # people to change the model, the prompt and the page range, none + # of which was the problem. + if pages_without_text: + raise ValueError( + "This document has no stored text to read. Its pages are indexed when it is" + " uploaded, and that index is what extraction reads — not the file. Re-process" + " the document and try again.") + raise ValueError( + "The model found no questions with a marked correct answer in these pages.") # Refresh DB connection — it may have gone stale during long LLM extraction from sqlalchemy import text as _text diff --git a/frontend/src/index.css b/frontend/src/index.css index 3f73d7c..e66cd24 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -758,18 +758,6 @@ html, body { overflow-x: hidden; } /* Mobile: hide sidebar, show toggle button */ .quiz-nav-toggle { display: none; } -.quiz-nav-mobile-grid { - display: flex; - flex-wrap: wrap; - gap: 6px; - padding: 14px; - margin-top: 10px; - background: var(--card-bg); - border: var(--card-border); - border-radius: var(--card-radius); - box-shadow: var(--card-shadow); - animation: fadeInUp 0.15s ease; -} /* ── Responsive ─────────────────────────────────────────────── */ @media (max-width: 768px) { diff --git a/frontend/src/pages/QuizPage.jsx b/frontend/src/pages/QuizPage.jsx index 5d10b58..27b0144 100644 --- a/frontend/src/pages/QuizPage.jsx +++ b/frontend/src/pages/QuizPage.jsx @@ -425,6 +425,7 @@ export default function QuizPage() { const [totalTime, setTotalTime] = useState(null) const [toast, setToast] = useState('') const [navOpen, setNavOpen] = useState(false) + const [drawerTab, setDrawerTab] = useState('questions') // The session rail is the navigator whenever there is room for it; the // dropdown only exists for screens too narrow to show it. Matches the // 1150px breakpoint in QuizPlayer.css that hides the rail. @@ -1218,8 +1219,9 @@ const timerStarted = timeLeft !== null every question, a button that unfolds the same list is noise. */} {!hasRail && ( )} @@ -1295,19 +1297,6 @@ const timerStarted = timeLeft !== null ) } - const QuestionDot = ({ q, i }) => { - const isActive = i === currentIdx - const isDone = !!answers[q.id] - return ( - - ) - } return (
Question{currentIdx + 1} of {totalCount}
) : ( - + /* Opens the same rail the desktop has, as a drawer. A dot grid + dropped under the bar was a different thing in a different + place doing the same job worse. */ + )}