Why I Built This
WhyIBuiltThis
Most students do not get lost in a whole lecture. They get stuck on one line. A term shows up before it is defined. A proof skips two steps. A formula assumes you remember last semester. Normal chatbots have no idea where you are on the page, so you have to paste context yourself. Summarizers are worse. They flatten the PDF into bullet points and strip the diagrams you actually need.
So you bounce between the PDF, Google, and a chat window, then scroll back to page 8 to see if the answer even matches what your lecturer wrote. Nothing checks whether you actually got it. LectureLift was built for that pause. The moment you stop reading because something did not make sense.
My Approach
MyApproach
The document stays primary. It takes about 70 percent of the screen. The tutor stays on the side and never covers your diagrams. When you upload a PDF, the backend hashes it, skips duplicates, stores it on Backblaze B2, and starts work in the background. It extracts text and boxes with unpdf, cleans broken blocks, finds real section boundaries with an LLM, then runs confusion detection in batches.
Frontend is Next.js App Router with server components for shells and thin client islands for the PDF reader. TanStack Query handles mutations, Zustand holds a read-only auth mirror. Backend is NestJS with Supabase Postgres and RLS, Vercel AI SDK with Gemini and Groq, Zod for structured outputs, and SSE for progress. That keeps the UI calm. You see extracting, cleanup, sections, then analysis. No spinners that lie.
Key Features
KeyFeatures
- PDF upload with content-hash deduplication and Backblaze B2 storage
- Confusion detection for jargon, missing prerequisites, concept jumps and unexplained abstractions
- Inline highlights anchored to PDF blocks and page numbers
- One-click tutor lessons that reconnect the missing concept to your actual page
- Streaming chat grounded in current page blocks and lesson context with page citations
- Section quizzes with lazy generation, resume, and lecture-wide mode
How the System Works
HowtheSystemWorks
You upload a PDF and keep reading. LectureLift works in the background and meets you at the line where you pause.
- You upload a PDF. Backend hashes the file. If it exists, you are linked to it. If not, it stores to B2 and marks the doc as pending
- Unpdf extracts blocks and page numbers. A cleaner merges fragments. An LLM detector splits the doc into titled sections
- Confusion detection runs in batches of 10 sections, two at a time. It tags passages with a type, severity, reason and fix, all linked to block ids
- The reader renders the PDF with pdfjs-dist and highlights those blocks inline. SSE pushes stage updates and analysis progress
- You click a highlight. LectureLift locks that friction point, generates a short lesson, and reconnects the idea to your exact page with math rendering
- Quizzes generate lazily. The first 10 sections are ready early. The rest generate when you scroll to them
- You can also ask the tutor. It pulls only your current page blocks and any open lesson, streams an answer, and saves page citations so you can jump back
Engineering Challenges & Lessons Learned
EngineeringChallenges&LessonsLearned
Lecture PDFs are messy. Headings look like body text, blocks split mid-sentence, and page boxes drift. I added a block cleaner that normalizes text and a section detector that uses the LLM only for boundaries, not for raw extraction. That gave stable block_index ranges to anchor highlights to.
Calling an LLM per section would be slow and expensive. Batching 10 sections at a time with concurrency of 2 cut cost a lot. Each batch validates block ids against what it sent, and per-batch rows track completed or failed so one bad batch does not kill the whole document. The UI can show partial results.
Two people clicking the same friction point at once could generate the lesson twice. The lesson call does an atomic update where lesson_status is pending or failed to generating. If a worker dies, the lock looks stale after 5 minutes and the next poll resets it.
Quiz generation needed to feel instant. The fix was to always expand a request to a full 10-section window and skip indices that already have questions. That way scrolling a little does not trigger 10 small calls, and retaking a section does not duplicate rows. Attempt scoring uses a Postgres RPC to increment safely.
What I’d Improve Next
WhatI’dImproveNext
- Spaced repetition for learned friction points using quiz scores and time since last seen
- Keep figures and tables as selectable regions so lessons can point at a diagram, not just text
- Instructor view to curate friction types and see a heatmap of where a cohort got stuck
- Better handling for very large PDFs with incremental analysis and offline caching of pages
