ESP32-S3 Bench Guide
ESP32-S3 N16R8 bench guide: live pinout, lessons, simulators and the bench log.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證A math whiteboard for kindergarten to 8th grade. Kids work problems by hand, and a tutor checks every line: a check mark for a right step, a circle around a slip, and a hint instead of the answer. Free for 7 days.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Bring your own keys. No AI credentials ship with this source; whoever runs an instance supplies their own keys (see SETUP.md).
License: FSL-1.1-ALv2. Read, modify and self-host freely; do not offer it as a competing service. Each version becomes Apache-2.0 two years after release. See LICENSE, NOTICE, CONTRIBUTING.md and THIRD-PARTY-NOTICES.md. The tldraw SDK is licensed separately and needs its own key in production.
An AI whiteboard that tutors students in real time. Students draw on a tldraw canvas; after a short idle pause the app captures the viewport, sends it to a vision model and draws feedback, hints or a worked solution back onto the canvas. Handwritten math is recognised with Mathpix, and an optional voice tutor (OpenAI Realtime) can look at the workspace and talk the student through it.
browser (Next.js App Router, React 19, tldraw 4)
| Supabase JS: email/password auth, whiteboards/user_settings/bug_reports via RLS
| authedFetch(): POST /api/* with Authorization: Bearer <supabase access token>
v
Next.js route handlers (src/app/api/**, Node runtime on Vercel)
| verify JWT -> rate limit -> validate body (zod) -> call provider
+-> OpenRouter text + vision models (Live checks, hints, worked steps, workspace analysis, credits); never image generation
+-> Mathpix handwritten math -> LaTeX (optional)
+-> OpenAI Realtime voice token (optional)
v
Supabase: Postgres (RLS) + Auth + Storage (board-assets, training-data)
Key directories:
| Path | What |
|---|---|
src/app/page.tsx | Dashboard: list / create / rename / delete boards |
src/app/board/[id]/page.tsx | The canvas, AI loop, autosave, voice tutor |
src/app/train/page.tsx | Trainer-only page that records before/after samples |
src/app/api/** | Route handlers (all require a Supabase JWT) |
src/lib/api-client.ts | authedFetch / apiJson / ApiError used by the client |
src/lib/server/** | Auth, env, rate limiting and provider helpers for routes |
supabase/migrations | Database schema (see below) |
docs/ARCHITECTURE.md | Request flows, security model, known limitations |
Defined in supabase/migrations/20260911000000_init.sql. Every table has RLS enabled, anon has no grants, and authenticated users can only see rows where user_id = auth.uid().
| Table | Purpose |
|---|---|
whiteboards | One row per board: title, data (tldraw snapshot jsonb), preview thumbnail, version, deleted_at (soft delete). Owner CRUD. |
whiteboard_snapshots | Board history for an operator's restore: the state a save replaced, at most every 10 minutes and before a wipe; newest 8 + one a day for 7 days, within 4 MB per board (20261003000000_snapshot_retention.sql). Written by trigger only; owner read. |
board_assets | Registry of objects in the board-assets bucket, for garbage collection. Owner CRUD. |
user_settings | features jsonb (feature-lab toggles). Owner read/insert/update. |
bug_reports | Insert-only from the app (message, screenshot, diagnostics, client log ring buffer). Read via the dashboard/service role. |
trainers | Allow-list of user ids who may use /train. is_trainer() is used by RLS. Self read only. |
training_samples | Before/after samples recorded by trainers (append-only). |
Storage buckets:
board-assets (public read, 15 MB, images): owner writes only under <uid>/<boardId>/.... Target home for canvas images so whiteboards.data stops carrying base64.training-data (private, 10 MB, PNG): trainers write/read only under <uid>/<sampleId>/....npm install
npx supabase start # needs Docker; prints API URL + anon key
npm run db:reset # applies supabase/migrations from scratch
cp .env.example .env.local # fill in the keys below
npm run dev # http://localhost:3000 (pretty pino logs)
Other scripts: npm run typecheck, npm run lint, npm test (vitest), npm run build, npm run db:push.
.env.local)Mirrors .env.example (which carries per-variable comments, sources and local defaults; src/__tests__/envExample.test.ts keeps the two in step with the code). Cloud/Vercel placement: docs/RUNBOOK-supabase.md section 5.
| Name | Required? | Used for |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | yes | Supabase project URL (browser client + server JWT verification + scripts) |
NEXT_PUBLIC_SUPABASE_ANON_KEY | yes | Supabase anon/publishable key (same users) |
OPENROUTER_API_KEY | yes | All image/vision/Live model calls and the credits banner |
OPENAI_API_KEY | no | Voice tutor (Realtime API). Missing -> /api/voice/token returns 503 |
MATHPIX_APP_ID | no | Handwritten math -> LaTeX (with MATHPIX_APP_KEY). Missing -> vision-only fallback |
MATHPIX_APP_KEY | no | See above; both must be set |
SUPABASE_SERVICE_ROLE_KEY | no | Server-only; declared in src/lib/env.ts but read by no route or script today |
NEXT_PUBLIC_TLDRAW_LICENSE_KEY | no | Removes the tldraw watermark (host-bound) |
NEXT_PUBLIC_SITE_URL | no | Sent as HTTP-Referer to OpenRouter; default http://localhost:3000 |
LOG_LEVEL | no | Server pino level, default info |
NEXT_PUBLIC_LOG_LEVEL | no | Browser pino level (console + bug-report log buffer), default info |
NEXT_PUBLIC_LIVE_MATH | no | 0 hides the Live Math pipeline/UI; default on |
LIVE_MODEL_CHECK | no | OpenRouter id override for Live check; default in src/lib/live/contracts.ts |
LIVE_MODEL_SOLVE | no | Same for Live solve |
LIVE_MODEL_VISION | no | Same for Live vision fallback |
BASE_URL | no (scripts) | scripts/live-smoke.mjs: dev server origin, default http://localhost:3113 |
MISTRAL_API_KEY is no longer used (the /api/ocr route it once powered has been removed with the image pipeline). Delete it from any environment.
The previous project was deleted, so production needs a fresh one:
npx supabase login
npx supabase link --project-ref <ref>
npm run db:push
This creates all tables, policies, triggers and both storage buckets. Re-running is safe.http://localhost:3000 to redirect URLs. Email/password sign-in is the only provider used.vercel env add NEXT_PUBLIC_SUPABASE_URL production
vercel env add NEXT_PUBLIC_SUPABASE_URL preview
vercel env add NEXT_PUBLIC_SUPABASE_URL development
vercel env add NEXT_PUBLIC_SUPABASE_ANON_KEY production
vercel env add NEXT_PUBLIC_SUPABASE_ANON_KEY preview
vercel env add NEXT_PUBLIC_SUPABASE_ANON_KEY development
Repeat for OPENROUTER_API_KEY, and for OPENAI_API_KEY / MATHPIX_* if you want voice and handwriting OCR. vercel env pull .env.local syncs the development values locally./train is gated server-side by the public.trainers table (the page also hides itself for non-trainers). After the trainer has signed up once, run in the SQL editor:
insert into public.trainers (user_id)
select id from auth.users where lower(email) = lower('[email protected]')
on conflict do nothing;
The app runs on Vercel (project whiteboardstaging). vercel.json is intentionally minimal; the Next.js preset handles everything.
vercel # preview deployment
vercel --prod # production
CI (.github/workflows/ci.yml) runs typecheck, lint, tests and a build on every push and PR to main with dummy env values, so no secrets are needed in GitHub.
| Symptom | Cause / fix |
|---|---|
Every /api/* call returns 401 unauthorized | The Supabase session expired or the token is for a different project. Sign out and back in; check NEXT_PUBLIC_SUPABASE_URL matches the project that issued the token. |
402 credits_exhausted | The OpenRouter account is out of credit. Top up at https://openrouter.ai/credits; the dashboard banner shows the balance. |
429 rate_limited | Per-user limit hit; the response carries Retry-After. Limits are per server instance. |
503 voice_unavailable | OPENAI_API_KEY is missing or invalid in that environment. Voice is optional; everything else keeps working. |
503 feature_unavailable | The route's optional provider (e.g. Mathpix) is not configured in this environment. |
502 upstream_error | OpenRouter/Mathpix/OpenAI returned an error; check server logs (LOG_LEVEL=debug). |
Board save fails with a timeout (Postgres 57014) | The snapshot is too large because images are still stored as base64 in whiteboards.data. Delete large pasted images, or ship the board-assets offload. |
npx supabase start fails | Docker is not running, or ports 54321-54329 are taken (npx supabase stop --no-backup then retry). |
All error responses from /api/* have the shape { "error": "<code>", "message": "<human text>" }.
Live Math is an always-on layer on the board (toggle next to the Off / Feedback / Suggest / Solve tabs, persisted per device in localStorage under agathon.live.v1). It turns each handwritten line into a native, editable KaTeX math shape about a second after pen-up and runs an offline mathjs engine for instant checks; a streaming LLM is used only for the why and for hints, and only when the help mode allows it.
What a student sees after writing a line:
| When | What |
|---|---|
| ~0.6 s | Quiet gate ends; strokes are clustered into lines and sent to POST /api/live/recognize (Mathpix v3/strokes, ~300 ms; vision-model fallback when Mathpix keys are absent). |
| ~1.2 s | A gray typeset "echo" appears right of the ink. The local engine adds a result chip (calculator rule), a green check or amber dot versus the previous line, a "Solved" chip on a correct final line, or a graph card for y = f(x). |
| ~2–3 s | In Suggest/Solve, a mismatch triggers POST /api/live/check (SSE); the first hint card streams in below the echo. "Solve steps" streams worked steps from POST /api/live/solve. |
Modes: Off = echoes and results only, never an LLM call. Feedback = location-only marks. Suggest = one Socratic hint per line. Solve = steps and full solutions on request. The legacy image-overlay pipeline is kept for non-math ink (diagrams) and via "Draw help" in the Live menu; its idle trigger is 4 s while Live is on.
What runs where:
src/lib/live/**, src/shapes/**, src/components/live/**).src/app/api/live/{recognize,check,solve}/route.ts behind the same auth, zod validation and per-user rate limits as every other route; prompts in src/lib/server/prompts/; streaming helpers in src/lib/server/sse.ts and openrouter.ts. Model ids default from LIVE_MODELS in src/lib/live/contracts.ts and can be overridden with LIVE_MODEL_CHECK, LIVE_MODEL_SOLVE, LIVE_MODEL_VISION.<Tldraw> mounts (board and train) so any saved board loads; NEXT_PUBLIC_LIVE_MATH=0 hides the pipeline and UI without unregistering the shapes.Keyboard: m selects the Math tool to type a LaTeX shape; double-click an echo to fix a misread line (the engine re-checks it without calling Mathpix).
Testing: npm test (356 unit tests incl. engine, clustering, policy, placement, shapes round-trip, SSE parsing), node scripts/live-smoke.mjs against a dev server + local Supabase (auth, recognition, streaming, rate limits), and the manual checklist in docs/LIVE-MATH-SPEC.md §10.3. Voice tools for Live (read_live_math, place_math, plot_function) are implemented and unit-tested but not yet wired into the voice session.
SMOKE_EMAIL | no (scripts) | scripts/live-smoke.mjs: throwaway local user, default [email protected] |
SMOKE_PASSWORD | no (scripts) | scripts/live-smoke.mjs: its password, default password123 |
SMOKE_SKIP_LLM | no (scripts) | scripts/live-smoke.mjs: 1 skips the LLM streams (no OpenRouter spend) |
RUN_DB_TESTS | no (tests) | 1 runs the DB/RLS integration tests in npm test against the local stack |