ESP32-S3 Bench Guide
ESP32-S3 N16R8 bench guide: live pinout, lessons, simulators and the bench log.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗This is a Next.js project bootstrapped with create-next-app.
First, run the development server:
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
Open http://localhost:3000 with your browser to see the result.
You can start editing the page by modifying app/page.tsx. The page auto-updates as you edit the file.
Set these values in your local .env.local and in your deployment provider:
GEMINI_API_KEY (required)GEMINI_MODEL (optional, default: gemini-3.1-flash-lite)GEMINI_SEARCH_MODEL (optional, default: the GEMINI_MODEL value; used only for web-search-grounded calls; gemini-2.5-flash is the recommended value when the lite model keeps answering without searching)GEMINI_MAX_OUTPUT_TOKENS (optional, default: 700)GRADE_MAX_SUBMISSIONS (optional, default: 40; a rarely-touched sanity ceiling, not the working limit - a wall-clock deadline, not this count, is what keeps an unattended run inside its platform time budget)GRADE_MAX_CHARS_PER_SUBMISSION (optional, default: 12000)GRADE_INTER_REQUEST_DELAY_MS (optional, default: 1200)GEMINI_ALLOW_LOW_TEMPERATURE (optional, default: false)GEMINI_THINKING_LEVEL (optional, default: unset)GEMINI_MIN_OUTPUT_TOKENS (optional, default: 512)The grading pipeline uses these limits to reduce free-tier quota spikes by capping per-run workload and pacing requests. A run that hits either the submission-count bound or an unattended run's wall-clock deadline reports the remaining students as not-graded (re-run to grade them) rather than silently dropping them. Per-call temperatures below 1.0 are dropped for Gemini 3.x models on Google's recommendation; set GEMINI_ALLOW_LOW_TEMPERATURE=1 to restore the previous behavior.
TAVUS_API_KEY (optional) — enables Avatar Studio (the Recording tab's likeness training and prompt-driven video generation). Server-side only; sent as x-api-key to https://tavusapi.com. Without it, the Avatar view explains what to set and disables training/generation instead of erroring on click. Note that custom face training also requires a paid Tavus plan — the free tier cannot train a likeness at all.The app supports more than one person, but sign-up is still deliberately
gated: an unapproved account must not be able to spend the shared
server-side credentials sitting behind it (Gemini, Canvas, GitHub, and so
on). Every signed-in identity has a row in public.app_users with a
role (owner or instructor) and a status (pending, active, or
suspended). Only an active account (owners included) can reach the app
or call a privileged server action; pending and suspended accounts are
redirected to /login with an explanation of why.
OWNER_EMAILS (required for a deployment to have an owner) —
comma-separated list of email addresses that are always owners. This used
to be the entire access-control model — the only addresses allowed to
sign in at all — and it no longer is: it now means "these accounts are
owners", not "these are the only accounts that exist". It is reconciled on
every sign-in (add an address and that account becomes an owner the next
time it signs in — no database edit needed), and it doubles as the
break-glass path: an allowlisted email resolves to owner even when
public.app_users is unreachable or that account's row is missing, so
whoever pays for the deployment can never be locked out by an outage or a
bad migration. It still fails closed exactly as before: with no allowlist
there is no owner, which means there is nobody who can approve anybody
else — the same fully-locked state the app is in today, not a new failure
mode.NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY (required) —
Supabase project used for authentication.Until an in-app sign-up flow ships, leave "Allow new users to sign up"
turned OFF in the Supabase dashboard (Authentication -> Providers -> Email).
There is no sign-up page in this app today (src/app/login/page.tsx is
sign-in only, plus the MFA step) and nothing in src/ reads SIGNUP_MODE or
SIGNUP_ALLOWED_DOMAINS outside of src/lib/signup-rules.ts's own test —
grep -rn "signup-rules" src turns up only that test file. So today there is
no code path — app-level or otherwise — standing between the public anon key
(which ships in every browser bundle by construction) and Supabase's own
POST /auth/v1/signup endpoint. With dashboard sign-ups left on, anyone can
call that endpoint directly: each call creates an auth.users row, fires the
trigger that inserts a matching public.app_users row, and sends a
confirmation email — burning the SMTP quota and sender reputation this README
asks you to set up below, with no code of this app's ever running. No access
is granted this way (the row lands pending and the request gate refuses
it), but the write and the email both already happened.
SIGNUP_MODE (optional, default approval) — reserved for the eventual
in-app sign-up form: approval (create pending, needs owner approval),
open (create active immediately), closed (hide the form and refuse
server-side). Has no effect today — nothing calls the module that reads
it.SIGNUP_ALLOWED_DOMAINS (optional) — reserved the same way: intended to
restrict sign-up to listed email domains (a leading dot also allows
subdomains). Has no effect today, for the same reason.These two are documented now so the intended contract is on record, but
setting either one changes nothing about who can create an account: sign-up,
once built, is expected to run browser-to-Supabase directly rather than
through this app's server, so an app-level mode can describe the intended
door but cannot itself gate the one Supabase already exposes — only the
dashboard toggle above does that. The real backstop in the meantime is that
every auth.users row — however it was created — gets a public.app_users
row that starts pending (the column's own default), and nothing short of an
owner approving it changes that.
Sign-in runs against Supabase Auth, but the dashboard still needs configuring for the parts this app cannot set from code:
auth.users rows. Re-enable it only once an
in-app sign-up flow that actually reads SIGNUP_MODE /
SIGNUP_ALLOWED_DOMAINS exists.GOOGLE_TOKEN_ENC_KEYGOOGLE_TOKEN_ENC_KEY (required once any Google, Microsoft, or — soon —
Canvas credential is stored) — a base64-encoded 32-byte AES-256-GCM key
(generate one with openssl rand -base64 32), read by src/lib/crypto.ts
and used to encrypt every stored third-party credential at rest for every
user. It was previously undocumented: it is not referenced anywhere else
in the repo — not in this README until now, not in any CI workflow, and
there is no example env file to list it in either; src/lib/crypto.ts is
the only functional reader. Rotating this key currently makes every
already-stored credential undecryptable. The stored payload does carry a
format-version prefix (v1:iv:tag:ciphertext; the older unversioned
iv:tag:ciphertext rows from before this prefix existed still decrypt too)
— but that prefix identifies the payload format, not which key encrypted
it, and there is still only the one GOOGLE_TOKEN_ENC_KEY slot, so
decryptSecret has no old key to fall back to and cannot distinguish "old
key" from "wrong key" either way. The version prefix is deliberate
groundwork for a future key-rotation scheme (a v2 branch that tries a new
key and falls back to an old one) without having to migrate every
already-encrypted row when that ships, but that scheme does not exist yet.
Losing the key, or changing it without a migration plan, today is
equivalent to revoking every user's Google, Microsoft, and (once shipped)
Canvas connections at once.| Variable | Does what | Required? | If missing |
|---|---|---|---|
OWNER_EMAILS | Comma-separated list of owner emails; reconciled on every sign-in; the break-glass path when the database is unreachable | Required for the deployment to have an owner | No owner exists, so no account can ever be approved — every request is refused (fails closed, same as today) |
NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY | Supabase project used for auth and data | Required | The Supabase client cannot initialize; nobody can sign in |
SUPABASE_SERVICE_ROLE_KEY | Service-role Supabase client (createServiceClient in src/lib/supabase/server.ts) used to look up an account's row when deciding access | Required for any non-owner account to reach the app | Every account lookup fails; every non-owner is refused as if unapproved, while an OWNER_EMAILS address is still admitted via the break-glass path (which never touches the database) — the app looks perfectly healthy to the owner while everyone else is locked out |
SIGNUP_MODE | Selects approval / open / closed for the eventual in-app sign-up form | Optional (default approval) | Not yet wired — has no effect regardless. Nothing in src/ reads it outside its own test. Falls back to approval's described behavior only once something reads it |
SIGNUP_ALLOWED_DOMAINS | Restricts sign-up to the listed email domains (a leading dot also allows subdomains), for the eventual in-app sign-up form | Optional | Not yet wired — has no effect regardless, for the same reason |
GOOGLE_TOKEN_ENC_KEY | AES-256-GCM key encrypting every stored Google/Microsoft/Canvas credential | Required once any such credential is stored | Saving or reading any such credential throws immediately |
MFA is supported and enforced once enrolled — it does not lock you out before setup (enforcement only triggers when a verified factor exists):
/account/security (there's a "Security" link in the
app header) and add an authenticator: scan the QR in your app and enter the
6-digit code to activate. Add a second authenticator as a backup.src/proxy.ts / src/lib/supabase/proxy.ts) and the
privileged server actions require the elevated (AAL2) session. This used
to be a middleware.ts file; Next.js replaced that file convention with
proxy.ts, which runs on the Node.js runtime instead of middleware's Edge
runtime — the request gate's logic is unchanged, but this affects
cold-start and per-request latency.Recovery: removing a lost authenticator requires the Supabase dashboard (Authentication -> Users -> the user -> Factors), and only the deployment's owner can reach that dashboard. If you are an instructor (not the owner) and lose access to all your authenticators, you cannot clear the factor yourself — contact your workspace administrator and ask them to remove it for you. This is why enrolling a backup authenticator is recommended.
When the in-app provider toggle is set to Other API, the matched features (course schedule, lecture deck, course materials, and the Copilot project prompt) call the Course Engine service instead of Gemini. All other features stay on Gemini regardless of the toggle.
COURSE_ENGINE_URL (optional, default: https://testing-knowledge-engine.vercel.app)COURSE_ENGINE_API_KEY (optional) — only required if the Course Engine project enforces a key; sent as X-API-Key. Never exposed to the client.Grading is handled by a separate dedicated service. When the toggle is Other API,
the Grading tab grades deterministically against a check-based rubric (CSV/JSON, e.g. the
materials rubric.csv) via that service instead of Gemini:
GRADING_ENGINE_URL (required to use the deterministic grader) — base URL of the grading service.GRADING_API_KEY (optional) — only required if the grading service enforces a key; sent as X-API-Key. Never exposed to the client.A third provider, Embedded Deterministic Engine, grades entirely in-process: no external service, no API key, no LLM. It builds a rubric of concrete, mechanically-checkable requirements (file types, word counts, required terms, function/class names, patterns) and awards each criterion's points by running its check against the submission. The same submission always scores the same.
Rubric precedence: a supplied rubric is used when present (the Canvas rubric, or
one pasted/uploaded); only when none is supplied is a rubric generated from the
assignment instructions by rule-based parsing (shown in the auto-generated rubric
panel for review). A supplied check-based rubric (JSON, or CSV with a check_type
column) is graded exactly; a free-text / criteria rubric is mapped to checks
heuristically (a warning says so). The rubric is capped at 4 criteria (generation
orders the most concrete checks first; a warning notes any that were dropped). It requires no configuration and works on both the ZIP upload
and Single Assignment (Canvas) sources. Because it runs in-process it keeps each
student's Canvas userId and files, so grades post back to Canvas and files preview
from the results table. The engine lives in src/lib/embedded-grader/. Only the
Grading tab is affected; every other feature stays on Gemini.
Discussion boards are graded differently from file/code work. When the embedded engine grades a Canvas discussion URL, it scores a few composite criteria built from the thread structure (which the Canvas pull now preserves: initial posts vs replies, timestamps, and who each reply targeted): Participation (initial post, reply count), Timeliness (posted before the due date), Engagement (replies to classmates, names a peer), and Quality proxies (asks a question, cites a source, covers the prompt's terms). It grades only what the prompt actually states: reply and word-count thresholds, and any multiple-day participation requirement, are added only when the prompt asks for them, so students are never graded on a requirement the instructor never wrote. The quality column is an honest proxy, not a judgment of depth or correctness, and a warning says so; finish that part in SpeedGrader. Grades still post back via the discussion's linked assignment.
The Grading tab can grade Canvas discussions and assignments directly from a
URL (Canvas has no UI export, but its API does). Choose Single Assignment as
the source and paste either a discussion link (.../discussion_topics/…) or an assignment
link (.../assignments/…) — the type is auto-detected from the URL. The
institution is selected automatically from the link's hostname, then each
student's work (discussion posts/replies, or assignment text + uploaded files) is
pulled via the Canvas API and graded against the rubric.
Canvas grading respects the provider toggle: Gemini uses the AI grader (needs assignment instructions; the rubric is synthesized if not provided), Other API sends the fetched work to the deterministic grading service as a synthesized Canvas-style zip (needs a check-based CSV/JSON rubric), and Embedded Deterministic Engine grades in-process against the Canvas rubric when present, otherwise a rubric generated from the instructions.
Both deterministic providers (Single Assignment source) only grade submissions that Canvas has not already marked graded.
After grading a Canvas URL you can review and post grades back to Canvas: edit each student's points and comment, then post. Grades write to the assignment's gradebook column and comments are added to each submission (assignment URLs, and graded discussions via their linked assignment). The token needs grading access. When the Canvas assignment has an attached rubric, posting also fills the SpeedGrader rubric with per-criterion points and comments (matched to criteria by name); otherwise just the overall grade and comment are posted.
Institutions are registered by hostname in src/lib/canvas.ts (e.g. MCC ->
canvas.mccneb.edu). Each registered institution reads its own credentials from
env vars prefixed with its code:
<CODE>_CANVAS_API_TOKEN (required for that institution) — an instructor personal access token (Canvas: Account → Settings → New Access Token). Sent as Authorization: Bearer, server-side only, never exposed to the client.<CODE>_CANVAS_URL (optional) — base URL override; defaults to https://<host>.MCC is preconfigured, so set MCC_CANVAS_API_TOKEN. To add another school: add an
entry to the CANVAS_INSTITUTIONS list (code, name, host) and set its
<CODE>_CANVAS_API_TOKEN.
Schools are registered by acronym (MCC, MPCC, …) in the Settings dropdown (top right). The list is stored in the browser and drives env-var lookups — set the secrets in the environment; the UI just selects which schools to use and shows whether each one's env is configured. Per acronym:
<ACRONYM>_CANVAS_URL — Canvas base URL (e.g. https://canvas.mccneb.edu). Required (a preconfigured school in CANVAS_INSTITUTIONS can fall back to its hard-coded host).<ACRONYM>_CANVAS_API_TOKEN — instructor token. Required.<ACRONYM>_LLM_URL / <ACRONYM>_LLM_API — that school's grading-service URL +
key for the deterministic ("Other API") grader. Optional; falls back to the
global GRADING_ENGINE_URL / GRADING_API_KEY.The Live Feed and Communications tabs share a single active-institution switcher (pick a school on one tab and the other follows).
The Grading tab's Live Feed sub-tab is a per-school dashboard of everything
waiting to be graded. Pick a school in the switcher; on open (and via Refresh)
it scans that school's active teacher courses for assignments and graded
discussions with needs_grading_count > 0, showing one row per
assignment/discussion with its description and rubric. The Auto Grade button
runs the exact same pipeline as the Single Assignment flow (grade → review matrix
→ post back to Canvas), routed to that school's grader. All polling and grading is
owner-gated (owner allowlist + MFA).
The Communications tab posts course announcements and replies to Canvas inbox messages, scoped to the institution chosen in the switcher (the same one the Live Feed uses). No new env vars beyond the per-acronym Canvas credentials above.
.../announcements, or assignment URL — only the .../courses/123 segment is
needed). Load the recent announcements, then compose a new one. The optional
Draft with AI button writes a title and body from a short instruction, which
you can edit; nothing posts until you click Post announcement. Announcements
are created as Canvas discussion topics flagged is_announcement.Every action is owner-gated (owner allowlist + MFA) like the rest of the Canvas features. The token needs announcement-create and conversation scopes, which an instructor personal access token already has.
/api/v1/lecture)When the provider toggle is Other API, generating a lesson on the Lesson Planning
tab calls the Course Engine lecture endpoint, which returns a finished PowerPoint
(module-lecture.pptx) built deterministically — no LLM. Content is retrieved from
trusted sources (Wikipedia/Wikiversity for explanations, Stack Overflow for code), so the
deck is bounded by what those sources provide. The call fans out per objective and per
concept, so it can take several seconds (allow 30s+; cold starts add more).
POST /api/v1/lecture on COURSE_ENGINE_URLX-API-Key: <COURSE_ENGINE_API_KEY> (server-side only, never exposed to the client)application/json):
objectives (required) — string or string array, 10–4000 chars, any format (list,
numbered/bulleted, or prose); capped at ~20 objectivestitle (optional, default "Module Lecture") — titles the deck and biases source
retrieval and language inference (e.g. a title of "Introduction to Python" resolves
"for loop" to the programming sense rather than a generic one).pptx (title slide, agenda, one explanation slide per objective,
per-concept Example: <Concept> slides with code for programming topics or prose
otherwise, and a cited-references slide). The client downloads it directly — there is no
in-app editable preview for this path.The Module Title field on the Lesson Planning form (shown only under the Other API
provider) supplies title; leaving it blank sends no title and the service applies its
"Module Lecture" default. Only objectives and title are wired from the app — the raw
endpoint also exposes the usual error envelope ({ "error": { "code", "message" } }) for
invalid input or auth failures.
The grader can now extract text from common source and document formats inside the uploaded zip archive, including:
txt, md, py, js, ts, tsx, jsx, java, c, cpp, cs, go, rs, json, xml, yaml, sql, and moredocx, pptx, xlsx (plus best-effort parsing for doc, ppt, xls)pdf, rtf, odt, odp, ods, html, csv, ipynbIf a file cannot be parsed, it is skipped and the rest of the submissions continue processing.
Each grading run returns:
This project uses next/font to automatically optimize and load Geist, a new font family for Vercel.
To learn more about Next.js, take a look at the following resources:
You can check out the Next.js GitHub repository - your feedback and contributions are welcome!
The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.
Check out our Next.js deployment documentation for more details.