Add “Make my homework”, and finish the mobile polish pass
Homework (/homework — entry points in the drawer and on the create screen) - Assignment intake reuses the study pipeline: pasted text, photos (camera/picker, compressed in the browser) and documents (PDF/DOCX/PPTX/ ODT/text) through lib/image + lib/documents, the same vision step and the same language guard. Raw files never leave the device — only text and JPEG data URLs reach the server. - Two large choice cards: “Just make it” returns the finished homework (answer only, plain text, copy + make it again); “Explain how to make this homework” builds an interactive guide. - The guide is one step per screen in the study-feed snap layout: explanation, three progressive hints (nudge -> hint -> the answer) and an optional checkpoint rendered by the existing activity components (graded through /api/grade). The last screen assembles the solution from the step answers, with a copy button. Checkpoints carry xp 0 so the guide stays free of XP/streak chrome. - Results live in freezmo.homework.v1 with a small history (open/delete); “Make it again” re-runs from the stored assignment text (the vision transcription when photos were used) — no original files are stored. - The answer and the guide are written in the assignment's language, never translated into the UI language. Server - POST /api/homework: NDJSON stage events like /api/generate, the same http-guard, auth checked before parsing, and its own RATE_LIMIT_HOMEWORK (documented in .env.example + the README table). - New prompts in lib/prompts.ts and zod schemas in lib/schemas.ts with the same single repair retry; TEXT_MODEL for content, VISION_MODEL only for photos. New shared client pieces: useNotesIntake, lib/homework store, lib/clipboard, and a shared NDJSON reader in lib/client-api. Also finishing the mobile polish pass from the previous request - Phone ice scene: the top-left orb moved behind the glass header (it sat behind the page heading/subtitle). The settings sheet's scroll cue is now a bare chevron inside a content fade (mask) instead of a chip over the status banner, and the API key card's note/disclosure/actions are tightened. - theme-color now follows the in-app theme (SSR metas still fall back to the OS preference); apple-mobile-web-app-status-bar-style stays “default” because iOS fixes the status-bar text colour at launch. Verified: 78/78 homework checks (text/photo/PDF, both modes, EN+CS, dark+light, 360/390px, copy, history persistence, storage privacy, API language, axe), plus the existing suites re-run (mobile 47/47, pixel contrast 0 below AA, i18n 27/27, desktop regression 72/72). Lint + build clean; service worker freezmo-v6.
This commit is contained in:
parent
9fb728d735
commit
ef2a6a3589
35 changed files with 2334 additions and 100 deletions
50
README.md
50
README.md
|
|
@ -79,6 +79,21 @@ no database, no data leaving your machine except the requests you send to Ollama
|
|||
and **Regenerate deck** — rebuilds the activities from the stored notes so you can keep drilling
|
||||
until you're ready
|
||||
|
||||
**Make my homework**
|
||||
|
||||
- Same intake as a deck — paste the assignment, photograph it, or add a PDF/DOCX/PPTX/ODT/text
|
||||
file; photos go through the same vision model and language guard that notes do
|
||||
- Two modes: **Just make it** returns the finished homework (the answer only, ready to copy), and
|
||||
**Explain how to make this homework** turns it into a guided walkthrough
|
||||
- The guide is one step per screen in the same snap-feed feel as the study feed: what to do and
|
||||
why, **three progressive hints** per step (nudge → hint → the full answer), and a **checkpoint**
|
||||
built from the normal activity components and graded through `/api/grade`
|
||||
- The last screen assembles the solution from the step answers, with a copy button
|
||||
- Results live in `freezmo.homework.v1` with a small history you can reopen or delete; **Make it
|
||||
again** re-runs from the stored assignment text — original files are never saved
|
||||
- The answer and the guide are always written in the **language of the assignment**, never
|
||||
translated into the interface language
|
||||
|
||||
**Activity types** (each one is its own React component, rendered from JSON by a `type` field)
|
||||
|
||||
| Type | Interaction |
|
||||
|
|
@ -335,12 +350,13 @@ the two pipeline steps stay separate either way.
|
|||
|
||||
## Rate limits
|
||||
|
||||
Generation is expensive (two model calls per deck), so the API routes are limited per client IP with
|
||||
a sliding window. Everything is configurable in `.env.local`:
|
||||
Generation is expensive (one to two model calls per request), so the API routes are limited per
|
||||
client IP with a sliding window. Everything is configurable in `.env.local`:
|
||||
|
||||
| Route | Default | Env var |
|
||||
| --- | --- | --- |
|
||||
| `POST /api/generate` | 12 per 5 minutes | `RATE_LIMIT_GENERATE` |
|
||||
| `POST /api/homework` | 12 per 5 minutes | `RATE_LIMIT_HOMEWORK` |
|
||||
| `POST /api/grade` | 40 per 5 minutes | `RATE_LIMIT_GRADE` |
|
||||
| `GET /api/health` | 90 per 5 minutes | `RATE_LIMIT_HEALTH` |
|
||||
| window for all of them | 5 minutes | `RATE_LIMIT_WINDOW_MS` |
|
||||
|
|
@ -581,20 +597,23 @@ app/
|
|||
page.tsx library ("Your icebox")
|
||||
create/page.tsx upload screen
|
||||
deck/[id]/page.tsx study feed for a saved deck
|
||||
homework/page.tsx “Make my homework” (assignment in, homework or guide out)
|
||||
api/generate/route.ts images/text in → NDJSON progress → deck JSON out
|
||||
api/homework/route.ts assignment in → NDJSON progress → homework/guide JSON out
|
||||
api/grade/route.ts short-answer grading
|
||||
api/health/route.ts Ollama reachability + model availability
|
||||
components/
|
||||
activities/ one component per activity type + shared shell
|
||||
Flashcard.tsx MultipleChoice.tsx TrueFalse.tsx FillBlank.tsx
|
||||
Matching.tsx Ordering.tsx ShortAnswer.tsx shared.tsx index.tsx
|
||||
homework/ make-my-homework screens (input, result, guide feed, history)
|
||||
UploadScreen.tsx StudyFeed.tsx ResultsScreen.tsx DeckCard.tsx ApiKeyGate.tsx
|
||||
InstallSheet.tsx TabBar.tsx ServiceWorkerRegistrar.tsx FolderBar.tsx
|
||||
LibraryScreen.tsx SettingsDialog.tsx AppHeader.tsx ThemeToggle.tsx icons.tsx ui.tsx
|
||||
lib/
|
||||
ollama.ts server-only HTTP client (timeouts, auth, error mapping)
|
||||
prompts.ts the vision, generation, grading and repair prompts
|
||||
ai.ts the three pipeline steps with zod validation + retry
|
||||
prompts.ts the vision, generation, grading, homework and repair prompts
|
||||
ai.ts every pipeline step with zod validation + retry
|
||||
schemas.ts zod schemas and shared types
|
||||
config.ts env config, cloud/direct model-name normalisation
|
||||
documents.ts client-side PDF/DOCX/PPTX/ODT/text extraction
|
||||
|
|
@ -607,6 +626,9 @@ lib/
|
|||
language.ts script/language checks that catch translated transcriptions
|
||||
ui-events.ts tiny event bus (any screen can open the settings dialog)
|
||||
storage.ts localStorage deck library, import/export
|
||||
homework.ts localStorage homework results + solution assembly
|
||||
use-notes-intake.ts shared client-side photo/document intake hook
|
||||
clipboard.ts copy helper with a non-secure-context fallback
|
||||
image.ts client-side validation, resize and compression
|
||||
client-api.ts browser client for the API routes (NDJSON reader)
|
||||
xp.ts utils.ts deck-ops.ts confetti.ts
|
||||
|
|
@ -629,10 +651,11 @@ scripts/
|
|||
| Route | Body | Returns |
|
||||
| --- | --- | --- |
|
||||
| `POST /api/generate` | `{ images?: string[], text?, difficulty, count?, focus?, language?, mode?, existing? }` | NDJSON stream: `{type:"stage"}`, `{type:"deck",deck,meta}`, `{type:"error"}` |
|
||||
| `POST /api/homework` | `{ mode: "make" \| "guide", images?: string[], text?, language? }` | NDJSON stream: `{type:"stage"}`, `{type:"homework",homework,meta}`, `{type:"error"}` |
|
||||
| `POST /api/grade` | `{ question, referenceAnswer, keyPoints, userAnswer, language? }` | `{ correct, score, feedback, missing }` |
|
||||
| `GET /api/health` | – | `{ ok, reachable, mode, baseUrl, hasApiKey, vision, text, latencyMs, modelCount, error }` |
|
||||
|
||||
All three routes accept an optional **`x-ollama-key`** header (the browser's own key; required in
|
||||
All four routes accept an optional **`x-ollama-key`** header (the browser's own key; required in
|
||||
`user-key` auth mode). Missing keys answer **401** with `kind: "needs_key"`, a rejected key answers
|
||||
`kind: "auth"` (or `kind: "no_credits"` for HTTP 402), and exceeding a rate limit answers **429**
|
||||
with `Retry-After`. Keys are used for the single request and discarded.
|
||||
|
|
@ -640,9 +663,9 @@ with `Retry-After`. Keys are used for the single request and discarded.
|
|||
Images are sent as base64 data URLs; the server strips the prefix before handing them to Ollama.
|
||||
Uploads are validated for type (photos and the document formats above), count (max 8 photos,
|
||||
6 documents, 12 images) and size (max ~8 MB per image after compression, 40 MB per document) and the
|
||||
client compresses aggressively, so a typical upload is 1–3 MB. `language` is optional on both routes:
|
||||
the vision step fills it in automatically, and the UI passes the stored deck language for grading,
|
||||
regeneration and “add more”.
|
||||
client compresses aggressively, so a typical upload is 1–3 MB. `language` is optional wherever it
|
||||
appears: the vision step fills it in automatically, and the UI passes the stored deck language for
|
||||
grading, regeneration and “add more” (and the assignment language for homework).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -870,6 +893,17 @@ Things I chose while building, with the reasoning:
|
|||
(hide the text → screenshot → worst-case background luminance per glyph rect): 0 runs below AA
|
||||
across library and settings, both themes, both languages.
|
||||
|
||||
35. **“Make my homework” reuses the study pipeline instead of growing its own.** The intake is the
|
||||
same client-side parsing as `/create` (lib/documents + lib/image, wrapped in `useNotesIntake`),
|
||||
the photos go through the same `extractNotes` vision step and language guard, and the guide's
|
||||
checkpoints are ordinary activity JSON rendered by the existing components — just a
|
||||
`checkpoint` field on a step. The route streams NDJSON like `/api/generate` and has its own
|
||||
rate limit. Two things are deliberately different: the finished answer and the guide are
|
||||
written in the *assignment's* language (the interface language never leaks in), and the final
|
||||
solution is assembled on the client from the step answers instead of asking the model for a
|
||||
second copy. Result records store the assignment text — the vision transcription when photos
|
||||
were used — so “Make it again” works without storing a single original file.
|
||||
|
||||
14. **`gpt-oss:120b` does all content work — but it cannot read images** (`this model does not
|
||||
support image input`), so `VISION_MODEL` has to stay a multimodal model (`gemma4:31b-cloud`).
|
||||
Text-only inputs — pasted text, PDFs with a text layer, DOCX/PPTX/ODT — never touch the vision
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue