diff --git a/README.md b/README.md index 6b5a967eb..de64146a1 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,20 @@ cd ai-engineering-from-scratch python phases/01-math-foundations/01-linear-algebra-intuition/code/vectors.py ``` +**Option B.1 — run the course site locally (offline + Code Lab).** + +```bash +cd ai-engineering-from-scratch +node site/local-server.mjs +``` + +Then open `http://127.0.0.1:5174/index.html`. + +For the student dashboard UI, open `http://127.0.0.1:5174/dashboard.html`. + +Frontend docs: `site/FRONTEND.md`. + +**Option C — find your level *(recommended)*.** Skip ahead intelligently. Inside Claude, Cursor, Codex, OpenClaw, Hermes, or any agent with SkillKit installed: **Option C — find your level *(recommended)*.** Skip ahead intelligently. Inside Claude, Cursor, Codex, OpenClaw, Hermes, or any agent with the curriculum skills installed: ```bash diff --git a/site/FRONTEND.md b/site/FRONTEND.md new file mode 100644 index 000000000..11cd9dfd2 --- /dev/null +++ b/site/FRONTEND.md @@ -0,0 +1,70 @@ +# Frontend (Local Student Dashboard) + +This repo ships a local-first student UI (Scaler-style): + +- `dashboard.html` → phase cards + global progress +- `phase.html` → per-phase lesson tiles + sidebar + progress +- `lesson.html` → lesson reader + sticky progress bar + Code Lab (run/test locally) + +All course content is read directly from this repo via a local server (no GitHub/raw content fetch). + +## Quick start + +From the repo root: + +```bash +cd ai-engineering-from-scratch +node site/local-server.mjs +``` + +Open: + +- Dashboard: `http://127.0.0.1:5174/dashboard.html` +- About/home: `http://127.0.0.1:5174/index.html` + +## How it works + +The server lives at: + +- `site/local-server.mjs` + +It provides: + +- Static files: `GET /` serves files from `site/` +- Local content: `GET /content/` serves files from the course repo (e.g. lesson markdown at `/content/phases/.../docs/en.md`) +- Directory listing: `GET /api/list?path=` returns local directory entries (used by lesson panels) +- Lesson meta: `GET /api/lesson-meta?path=phases/.../...` returns metadata and resolved runnable files +- Runner APIs: + - `POST /api/run` executes `python3` for a selected file + - `POST /api/test` runs `pytest` if installed, else `unittest` discovery +- Rubrics/AI hooks: + - `GET /api/rubric?path=...` returns `rubric.json` if present, else a generated rubric + - `POST /api/review` currently returns `501` (stub) until an LLM provider is wired up + +## Code Lab + +In `lesson.html`, the **Code Lab** panel: + +- auto-detects runnable artifacts via `/api/lesson-meta` (uses `catalog.json` + filesystem fallback) +- fetches the runnable file content from `/content/...` +- runs code via `/api/run` +- runs tests via `/api/test` + +Notes: + +- `Run` currently runs Python (`python3 -I ...`). Other languages are listed (Julia/TS/etc.) but not executed yet. +- Tests only run if the lesson directory includes a `tests/` folder or `test_*.py` files. + +## Progress tracking (local only) + +Progress is stored in browser `localStorage` by: + +- `site/progress.js` + +Resetting progress clears local completion + quiz answers for this browser only. + +## Next steps (planned) + +- Add non-Python runners (Node/TS, Julia, Rust) behind the same API. +- Implement `/api/review` with a configurable LLM provider and per-lesson rubrics. + diff --git a/site/app.js b/site/app.js index 4092ae744..c2284b52d 100644 --- a/site/app.js +++ b/site/app.js @@ -50,13 +50,12 @@ var lessons = PHASES[i].lessons; totalLessons += lessons.length; for (var j = 0; j < lessons.length; j++) { - var staticDone = lessons[j].status === 'complete'; var userDone = false; if (hasProgress && lessons[j].url) { var lp = window.AIFSProgress.extractPath(lessons[j].url); if (lp) userDone = window.AIFSProgress.isLessonComplete(lp); } - if (staticDone || userDone) completeLessons++; + if (userDone) completeLessons++; } } var completePhases = 0; @@ -113,13 +112,12 @@ var total = p.lessons.length; var done = 0; for (var j = 0; j < p.lessons.length; j++) { - var staticDone = p.lessons[j].status === 'complete'; var userDone = false; if (hasProgress && p.lessons[j].url) { var lp = window.AIFSProgress.extractPath(p.lessons[j].url); if (lp) userDone = window.AIFSProgress.isLessonComplete(lp); } - if (staticDone || userDone) done++; + if (userDone) done++; } var statusClass = p.status.replace(/ /g, '-'); var roman = toRoman(p.id); @@ -236,11 +234,12 @@ var statusClass = l.status.replace(/ /g, '-'); if (userComplete) statusClass = 'complete'; + else statusClass = 'planned'; html += '