AI Learning lab - #3
Conversation
|
Warning Review limit reached
Next review available in: 39 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (4)
📒 Files selected for processing (25)
WalkthroughSummaryMentora adds a complete learning platform with authentication, AI tutoring, content ingestion, adaptive study tools, progress tracking, Cloudflare deployment configuration, and automated tests. ChangesMentora platform
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Learner
participant Browser
participant Worker
participant TutorAPI
participant AIService
participant D1
participant Vectorize
Learner->>Browser: authenticate and select concept
Browser->>Worker: submit content or tutoring request
Worker->>TutorAPI: route authenticated request
TutorAPI->>AIService: ingest content or generate response
AIService->>Vectorize: store or retrieve embeddings
AIService->>D1: persist content, sessions, reviews, or quizzes
TutorAPI-->>Worker: return structured learning data
Worker-->>Browser: return JSON response
Browser-->>Learner: render study content and progress
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Introduces “Mentora”, an adaptive AI tutoring platform on Cloudflare Python Workers, combining authenticated learner accounts, RAG-backed tutoring, adaptive study tools (flashcards/quizzes), spaced repetition, prerequisite mapping, and a progress dashboard.
Changes:
- Adds a Cloudflare Worker API + D1/Vectorize/KV bindings for auth, tutoring sessions, ingestion/retrieval, study tools, and progress endpoints.
- Implements adaptive tutoring logic (mode selection, scoring, SM-2 updates), prerequisite gap detection, and study tool generation/parsing.
- Adds a static frontend (auth, chat, upload, flashcards, quiz, progress) plus migrations/tests/docs.
Reviewed changes
Copilot reviewed 26 out of 29 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| wrangler.toml | Worker + bindings configuration (D1/Vectorize/KV/assets) and build hook. |
| migrate.sh | Script to apply D1 migrations (local/remote). |
| migrations/0001_schema.sql | Initial D1 schema for users, concepts, sessions, messages, chunks. |
| migrations/0002_hardening.sql | Ownership + review tracking additions and indexes. |
| migrations/0003_study_tools.sql | Quiz tables + indexes. |
| migrations/0004_progress_dashboard.sql | Streak table for dashboard. |
| schema.sql | Consolidated schema snapshot for reference/local setup. |
| src/worker.py | Edge routing + auth endpoints + page routing into static assets. |
| src/auth.py | Token creation/verification + password hashing + PII encryption envelope. |
| src/scholar/api.py | Authenticated API handlers for tutoring, ingestion, study tools, progress. |
| src/scholar/ai_service.py | Embeddings, Vectorize upsert/delete/query, Gemini response + JSON parsing. |
| src/scholar/concept_engine.py | Tutor session loop: retrieval, prompting, message persistence, scoring, SM-2. |
| src/scholar/spaced_rep.py | SM-2 updates, due concept selection, calendar grouping, engagement prefs. |
| src/scholar/prompts.py | System prompt templates and prompt builder. |
| src/scholar/prereq_mapper.py | Prerequisite graph operations + LLM prereq-gap verification. |
| src/scholar/init.py | Package marker for scholar module. |
| src/init.py | Package marker for src module. |
| public/index.html | Auth UI (login/register) landing page. |
| public/chat.html | Learning Lab chat UI + concept sidebar + session handling. |
| public/upload.html | PDF/text ingestion UI with PDF.js extraction + source management. |
| public/flashcards.html | Flashcards UI consuming generated study artifacts. |
| public/quiz.html | Quiz UI for generation and submission/scoring flow. |
| public/progress.html | Progress dashboard UI (mastery overview, calendar, charts, streaks). |
| tests/test_auth.py | Unit tests for token, password hashing, secret validation, PII envelope. |
| tests/test_ai_service.py | Async tests for ingestion batching, cleanup, retrieval ownership, prompts. |
| tests/test_study_tools.py | Tests for study normalization, calendar grouping, JSON parsing. |
| README.md | End-to-end setup, resource bindings, Vectorize metadata indexes, deploy steps. |
| .dev.vars.example | Example local secret values for Wrangler .dev.vars. |
| .gitignore | Ignores local vars, Wrangler state, Python/Node build artifacts. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Actionable comments posted: 48
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.gitignore:
- Line 1: Update the .dev.vars ignore pattern to match all environment-specific
variants using .dev.vars*, and add an exception so .dev.vars.example remains
tracked.
In `@public/chat.html`:
- Line 62: Remove the non-functional Profile anchor from the dropdown in
public/chat.html, since no profile page exists; alternatively replace it with a
clearly disabled, screen-reader-accessible “Profile (coming soon)” element.
- Around line 305-327: Replace the interpolated onclick handlers in
renderConcepts and loadDueConcepts with JavaScript event listeners that pass the
concept ID directly, removing the encode/decode round trip. Add role="button"
and tabindex="0" to each clickable card, and bind Enter/Space keyboard handlers
that trigger the same selection action while preventing default behavior.
In `@public/flashcards.html`:
- Around line 106-112: Update the response handling in the flashcard fetch flow
to redirect to `/` when response.status is 401 or 403, before calling showError;
preserve the existing error display for other non-OK responses.
- Around line 116-120: In the no-cards branch, remove the redundant
status.textContent assignment and replace the innerHTML construction with DOM
APIs that create the line break and upload link, set the link’s text, className,
and href using conceptId, then append them to status. Apply the same DOM-based
construction to the matching innerHTML block in quiz.html.
In `@public/index.html`:
- Line 43: Add role="alert" to the error/status message containers `#error-box`,
`#status-error`, `#status-success`, and `#status` in the referenced HTML templates so
dynamically inserted authentication, upload, flashcard, and quiz messages are
announced immediately by assistive technology. Preserve the existing classes and
show/hide behavior.
- Around line 32-40: Add tab semantics to the login and register controls: mark
the buttons as tabs within a tablist, assign matching tabpanel relationships,
and set the initial active state with aria-selected. Update switchTab to keep
aria-selected synchronized with the visual selection, and add type="button" to
both buttons.
In `@public/progress.html`:
- Around line 309-321: Update renderMasteryCards to derive todayStr with the
existing formatLocalDate helper so due-date comparisons use local dates
consistently with renderCalendar. In renderDueSection, replace timestamp
subtraction from new Date(c.due_date) with date-string comparison against
formatLocalDate(today), preserving the existing overdue count while avoiding
UTC/local mixing.
- Around line 9-10: Update both CDN script tags in the progress page to use the
tested Chart.js 4.5.1 and chartjs-adapter-date-fns 3.0.0 release URLs, and add
the corresponding integrity SRI hashes plus crossorigin="anonymous" attributes
to each tag.
In `@public/quiz.html`:
- Line 155: Require an explicit learner action before invoking generateQuiz:
replace the page-load call in public/quiz.html with a visible “Start quiz”
control and wire its click handler to generateQuiz. Ensure quiz generation no
longer occurs automatically on page load.
In `@public/upload.html`:
- Around line 521-535: Replace silent API failure handling with explicit errors
across all listed sites: in public/upload.html lines 521-535, update
deleteSource to use readError and call showStatusError for both non-ok responses
and caught exceptions; in public/progress.html lines 231-233, update loadData’s
catch to render an error banner; and in public/upload.html lines 471-478, update
loadSources to display an error message in the table body for non-ok responses
and caught exceptions. Add and reuse the page-appropriate readError helper to
extract backend error messages with fallbacks.
- Around line 68-146: Add matching for attributes to the labels associated with
concept-select, new-concept-label, new-concept-slug, paste-text-input, and
source-label-input. Add an aria-label to the visually hidden pdf-file-input so
its purpose is announced while preserving the existing drop-zone interaction.
In `@src/auth.py`:
- Around line 22-35: The placeholder validation in _is_placeholder and
required_secret must reject repository-known example secrets before
authentication, including replace-with-... values; add an explicit generic
placeholder rule or equivalent entries. Update .dev.vars.example lines 2-3 to
use the PLACEHOLDER sentinel rejected by required_secret, while preserving
normal validation for configured secrets.
- Around line 144-145: Update hash_pii to return a domain-separated HMAC using
the managed lookup secret instead of an unkeyed SHA-256 digest, while preserving
the existing normalization. Migrate existing email_hash and username_hash
records or implement dual-read support so current lookup values remain usable
during the transition.
- Around line 158-180: The encrypt_aes flow currently emits a custom v2
stream-cipher envelope; replace it with an asynchronous AES-GCM implementation
using js.crypto.subtle and a new version prefix, preserving compatibility by
retaining decryption for the existing v2 format and _decrypt_legacy. Update all
encrypt_aes callers and migration logic for asynchronous Web Crypto operations,
re-encrypt migrated records with the new version, and add coverage for both
formats and tamper rejection.
- Line 19: Update hash_password() and verify_password() to version stored
password hashes with their algorithm and iteration count, while retaining
compatibility with existing salt:hash values generated using 260000 iterations.
Verify each hash using its recorded or legacy work factor, and after successful
verification rehash it with the current PASSWORD_ITERATIONS and persist the
upgraded value.
In `@src/scholar/ai_service.py`:
- Around line 301-302: Update the RAG retrieval flow around owned_vector_ids so
it returns the empty-result value immediately when the user owns no vectors,
before the Workers AI embedding and Vectorize query calls. Preserve the existing
no-owned-vectors diagnostic if appropriate, and keep normal retrieval unchanged
when owned_vector_ids is non-empty.
- Around line 398-405: Add a 30-second timeout to the Gemini request in the
fetch flow by assigning js.AbortSignal.timeout(30000) to options.signal before
calling js.fetch. Preserve the existing non-OK response handling, and handle the
timeout exception if required to return the expected learner-facing error.
In `@src/scholar/api.py`:
- Around line 366-373: The flashcard generation path currently discards results
on every request. Update the flashcard handler around _normalise_flashcards to
persist generated cards in a tutor_flashcards store keyed by user and concept,
return existing cards when valid, record regeneration time, and expose
regeneration as an explicit invalidation path. Invalidate the stored deck when
material for the concept is ingested or deleted.
- Around line 212-214: Update the material-loading call in the surrounding API
flow to pass an explicit max_chars value to
SharedAIService.get_uploaded_material, using a constant sized for the configured
Gemini model’s context window. Keep the returned material flowing unchanged to
handle_flashcards and handle_quiz_generate.
- Around line 850-861: Update _calculate_streak so the cursor begins on today
when today appears in study_dates, otherwise begins on yesterday, preserving
consecutive-day counting without resetting during an unfinished current day. Add
a unit test in tests/test_study_tools.py covering activity yesterday with no
activity today and asserting the streak remains active.
- Around line 87-95: Update get_authenticated_user so database lookup failures
are not converted to None, which callers interpret as invalid authentication;
remove the broad suppression and let the lookup exception propagate to the
existing handler error wrapper, or return a distinct failure sentinel that
handlers map to 503. Preserve None exclusively for missing or invalid user
tokens.
- Line 26: Set MAX_CONTENT_LENGTH to a concrete upper bound, such as 1,000,000
characters, and ensure _bounded_text rejects content exceeding that limit with a
clear 400 or 413 response before ingest_lesson creates chunks or performs vector
writes. Preserve normal ingestion for content within the limit.
- Around line 711-715: Update the response handling after add_prereq_edge in the
prerequisite-edge handler to split success and failure outcomes. Return the
existing success body only when edge_id is truthy; when it is falsy, return HTTP
400 with an error body containing data.error and a useful message instead of
status "ok" and a null edge_id.
- Around line 676-692: Update the deletion flow around
SharedAIService.delete_vectors to filter out null or empty vectorize_id values,
and change the failure response to acknowledge that some vectors may already
have been removed while asking the user to retry. Prefer deleting the matching
content_chunks rows before calling delete_vectors so database deletion failures
preserve content and successful vector deletion may only leave harmless orphaned
vectors; retain the existing user and concept scoping.
- Around line 520-570: The quiz submission flow around sm2_update and the final
tutor_quizzes UPDATE must make the quiz claim, SM-2 writes, and completion
finalization atomic. Use a single D1 batch or equivalent idempotent transaction,
keep completed_at IS NULL in the final update, and inspect the affected-row
count via the runtime result’s meta.changes; treat zero changes as a conflict
without leaving SM-2 writes committed.
In `@src/scholar/concept_engine.py`:
- Around line 45-56: Update build_system_prompt to use a module-scope import of
get_tutor_prompt from scholar.prompts, then remove the try/except and all
fallback prompt strings. Return get_tutor_prompt(mode, concept_node) directly so
import failures surface and adaptive prompt inputs remain intact.
- Around line 192-196: Update the batch execution in the message persistence
flow to call batch on the injected db handle used by stmt_u, stmt_a, and stmt_s,
rather than env.DB. Preserve the existing statement preparation and ordering
while ensuring all statements are executed on the same database instance.
- Around line 111-140: Centralize response scoring in
SharedAIService.score_response: in src/scholar/concept_engine.py lines 111-140,
remove the module-level score_response and update handle_tutor_turn to call
SharedAIService(env, user_id).score_response(...). In src/scholar/ai_service.py
lines 443-454, retain the service scorer, format prompts.SCORING_PROMPT with
concept_id, and remove the unused-argument warning; in src/scholar/prompts.py
lines 25-28, keep SCORING_PROMPT as the sole prompt definition. In
src/scholar/ai_service.py lines 16-20, delete the duplicate local constant and
import the shared prompt. Ensure scoring failures do not pass a neutral 3 into
SM-2 updates.
- Around line 177-180: Update the history query in the concept-engine
history-loading flow around _results_to_list and list(reversed(...)) to order
messages by created_at DESC with rowid DESC as the deterministic tie-break,
matching the existing rowid ordering pattern while preserving chronological
reconstruction.
- Around line 214-220: Tighten the confusion detection in the user-message
signal logic by matching “help” as a standalone word rather than using a
substring check. Preserve the existing “confused” and “don’t understand” checks
and continue updating the “confusion” preference only for genuine confusion
signals.
In `@src/scholar/prereq_mapper.py`:
- Around line 94-104: Replace the local regex/json parsing in the prereq
verification flow with SharedAIService.generate_json, and pass a short
instruction as the user turn rather than duplicating user_message already
embedded in prompt. Apply the same parser and user-turn adjustment in
suggest_prereqs, preserving the existing has_prereq_gap decision and fallback
behavior.
- Around line 133-163: Update add_prereq_edge to enforce edge uniqueness at the
database level with a unique (user_id, source_id, target_id, edge_type)
constraint or index, and handle conflicts safely rather than relying only on the
existing-row check. Before inserting, traverse learner_edge from target_node_id
to determine whether source_node_id is already reachable, rejecting the insert
if it would create a cycle. Add pagination or a bounded result cap to
get_prereq_graph for both node and edge queries while preserving its graph
response shape.
- Around line 66-85: Update check_prereqs to collect valid edge source IDs,
fetch all matching concept_node records with one parameterized IN query whose
placeholders are generated from the ID count, and build unmet_prereqs from the
returned rows while preserving the mastery threshold and ordering. Replace the
unused lowest_mastery unpacking with _lowest_mastery.
- Around line 114-131: Update the return annotation of add_prereq_edge to str |
None, preserving its existing None returns for missing nodes and self-edges
while retaining the string edge ID return path.
In `@src/scholar/prompts.py`:
- Around line 25-39: Update SCORING_PROMPT and PREREQUISITE_PROMPT to clearly
delimit {user_message} as untrusted learner content, matching the pattern used
by _build_context_prompt. Add explicit instructions that the delimited text is
data to analyze, not instructions to follow, while preserving the
scoring-only-digit and prerequisite JSON response formats.
In `@src/scholar/spaced_rep.py`:
- Around line 12-38: Create a shared src/scholar/d1_utils.py exporting
_row_to_dict and _results_to_list, preserving the tolerant to_py and
js.JSON.stringify conversion behavior while replacing bare Exception catches
with expected conversion errors and logging failures. Remove the duplicate
helpers from src/scholar/spaced_rep.py#L12-L38,
src/scholar/concept_engine.py#L16-L42, and src/scholar/prereq_mapper.py#L14-L40,
then import the shared helpers in each module; also consolidate the near-copy in
ai_service.py to use the shared _results_to_list.
- Around line 71-89: Update the date handling in the spaced-repetition flow,
including the functions containing this streak logic, get_calendar_data, and
get_due_concepts, to derive today from the learner’s stored timezone or
review-request offset instead of date.today(). Preserve consistent local-day
comparisons for streaks, calendar data, and due concepts; if UTC is
intentionally retained, document that decision at each flagged usage.
- Around line 41-62: Update sm2_update to return dict | None, reflecting its
missing-row behavior. Clamp quality to the valid 0–5 range before the
success/failure branches and easiness calculation, so interval and easiness
updates use only bounded input while preserving existing behavior for valid
values.
In `@src/worker.py`:
- Around line 166-175: Normalize the email with strip() and lower() before
passing it to hash_pii in the login flow around _row_to_dict and
verify_password, matching the normalization used during registration. Add a
regression test that registers and then logs in using mixed-case email input.
- Around line 261-262: Update the extensionless page-route collection used by
the request handler to include /flashcards and /quiz alongside /chat, /progress,
and /upload, so each maps to its corresponding .html asset. Also update the
flashcards and quiz links in chat.html to use extensionless URLs and preserve
their existing query parameters.
- Around line 40-48: Update cookie_response so its max_age default references
TOKEN_TTL_SECONDS instead of duplicating the 3600-second literal. Preserve
explicit max_age overrides and the existing cookie header behavior.
In `@tests/test_ai_service.py`:
- Around line 262-311: Add focused async tests for get_uploaded_material
covering preserved chunk order, max_chars truncation at the final chunk, and the
default "uploaded material" source label when source_label is missing. Use the
existing make_service and FakeDB.retrieve_result fixtures, and assert exact
returned text/source values to cover ordering, budget exhaustion, and fallback
behavior.
- Around line 21-31: Move the shared Workers runtime setup into tests/conft.py,
including the src path insertion and the _FakeJSON, _FakeResponse, js, workers,
and auth stubs. Remove the duplicated import-time stub definitions and path
setup from tests/test_ai_service.py and tests/test_study_tools.py, while
preserving their existing test behavior and meaningful assertions; verify the
suite remains isolated and passes regardless of collection order.
In `@tests/test_auth.py`:
- Around line 27-36: Expand the auth tests around create_token and verify_token
to assert expired tokens, using ttl_seconds=-1 or a manually signed expired
token, and rename test_token_round_trip_and_expiry if expiry coverage is
omitted. Add validation-failure cases for alg: "none", secrets shorter than
MIN_SECRET_LENGTH, and malformed token shapes, preserving the existing valid
round-trip, wrong-secret, and tampering assertions.
In `@tests/test_study_tools.py`:
- Around line 72-75: Extend test_study_difficulty_follows_mastery to assert
_study_difficulty returns the expected classifications at mastery values 0.4 and
0.7, locking the strict threshold behavior. Add coverage for invalid or
non-numeric mastery values that exercises the except (TypeError, ValueError)
fallback in _study_difficulty, using descriptive assertions for the fallback
result.
- Around line 110-120: Update test_calendar_groups_due_concepts_by_date to use
unittest.IsolatedAsyncioTestCase and await get_calendar_data directly instead of
invoking it through __import__("asyncio").run. Replace date.today()-based input
with a deterministic pinned date, while preserving the assertion that verifies
de-duplication and alphabetical ordering; remove date and timedelta imports if
they become unused.
In `@wrangler.toml`:
- Around line 7-8: Remove the migrate.sh invocation from the [build] command in
wrangler.toml. Update migrate.sh to require an explicit remote-production
argument before applying remote D1 migrations, while preserving local usage. In
README.md, document migrations as a separate protected release stage and
describe the required explicit argument; apply these changes at wrangler.toml
lines 7-8, migrate.sh lines 4-7, and README.md lines 138-147.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: alphaonelabs/coderabbit/.coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: ecbbf4c9-e9cf-4004-98dc-6b6f6abb0e6a
⛔ Files ignored due to path filters (4)
migrations/0001_schema.sqlis excluded by!**/migrations/**migrations/0002_hardening.sqlis excluded by!**/migrations/**migrations/0003_study_tools.sqlis excluded by!**/migrations/**migrations/0004_progress_dashboard.sqlis excluded by!**/migrations/**
📒 Files selected for processing (25)
.dev.vars.example.gitignoreREADME.mdmigrate.shpublic/chat.htmlpublic/flashcards.htmlpublic/index.htmlpublic/progress.htmlpublic/quiz.htmlpublic/upload.htmlschema.sqlsrc/__init__.pysrc/auth.pysrc/scholar/__init__.pysrc/scholar/ai_service.pysrc/scholar/api.pysrc/scholar/concept_engine.pysrc/scholar/prereq_mapper.pysrc/scholar/prompts.pysrc/scholar/spaced_rep.pysrc/worker.pytests/test_ai_service.pytests/test_auth.pytests/test_study_tools.pywrangler.toml
Summary
This PR introduces an adaptive AI tutoring platform built with Cloudflare Python Workers. It combines authenticated learner accounts, RAG-powered tutoring, adaptive study modes, spaced repetition, prerequisite mapping, generated study tools, and progress tracking.
What changed
Added Cloudflare Worker API with:
Added adaptive tutoring logic:
Added RAG ingestion and retrieval:
Added prerequisite mapping:
Added study tools:
Added progress dashboard:
Added frontend pages:
Added database schema and migrations for:
Added setup and deployment documentation for local and production Cloudflare environments.
Security and data handling
Infrastructure
The application is configured for:
Testing
Validated with:
Result:
Coverage includes authentication, token validation, PII integrity, embedding batches, Vectorize retrieval ownership, ingestion cleanup, flashcard and quiz normalization, study profiles, and review calendar grouping.
Deployment notes
Before deployment:
user_idandconcept_id.GEMINI_API_KEYTOKEN_SECRETENCRYPTION_KEYSummary
User impact
Learners can create accounts, upload study material, receive adaptive tutoring, practice with generated study tools, and track mastery over time. The platform includes safeguards for authentication, personal data, and deleted or failed ingestion data.