ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A pre-submission service for students checking their own academic writing. It offers three separate services:
The UI keeps these apart everywhere: free-scan results are labelled "Preliminary risk estimate · not a Turnitin result"; screening results are labelled as the observed result of that screening run. Missing values are shown as "Not returned", never estimated.
Turnitin is a third-party service. This platform is independently operated and is not affiliated with, endorsed by, or operated by Turnitin. The app contains no Turnitin branding, UI, credentials, scraping or automation.README 전문 보기 ↗A pre-submission service for students checking their own academic writing. It offers one service, the automatic report (the free scan was removed on 2026-10-25, and every account now gets one free full report a day instead, see freeReport in config/app.ts; Writing Refinement was removed on 2026-10-26, its paid orders refunded as credits and its open orders cancelled by migration 0022):
| Service | What it is | Where the result comes from |
|---|---|---|
| AI-Writing Risk Report (automatic, 1 credit per 1,000 characters, minimum 2 credits, first 8,000 of every 20,000 characters charged and the rest free, 500–100,000 characters) | Our own reviewer (DeepSeek) reads the whole pasted text in pieces and the answers are stitched into one report | Overall level, paragraph map, marked sentences, grammar spots and a PDF. Not a Turnitin result and no Turnitin-like percentage |
The UI keeps these apart everywhere: the report is labelled as our own reviewer's guidance. Older hand-made Turnitin reports still display as the observed result of that run.
Turnitin is a third-party service. This platform is independently operated and is not affiliated with, endorsed by, or operated by Turnitin. The app contains no Turnitin branding, UI, credentials, scraping or automation.
The brand name is "0%". Rename it in config/app.ts (brand); nothing else hard-codes it.
ScreeningProvider interface, so a properly licensed source can be swapped in without touching the frontend.USER ─► NEXT.JS (App Router)
└─ Paid orders ─ Stripe Checkout ─► webhook ─► queue
└─ Admin workflow (human) ─► TURNITIN (outside the app)
└─► screening_results + private report ─► user dashboard
Supabase: Auth · Postgres (RLS) · private Storage
proxy.ts instead of middleware), TypeScript strict, Tailwind v4, Framer Motion, react-three-fiber (hero only, lazy-loaded, desktop only, respects reduced motion).| Path | Purpose |
|---|---|
config/pricing.ts | All prices (HKD cents). Nothing else hard-codes a price. |
config/app.ts | Brand, daily limit, upload limits, retention periods. |
config/services.ts, config/faq.ts | Service copy, disclaimers, FAQ. |
lib/orders/status.ts | The order state machine: labels, flows, allowed transitions. |
lib/network.ts | Salted hash of the visitor's network address, for the daily free report's per-address cap. |
lib/screening/ | ScreeningProvider interface and ManualTurnitinProvider (human-in-the-loop). |
lib/payments/ | Stripe client and markOrderPaid (idempotent, amount-checked), used by the webhook and by admin claim confirmation. |
config/payments.ts | Alipay / PayMe / bank transfer payee details, QR paths and instructions. |
app/actions/ | Server actions: orders, admin, account. |
app/api/files/[kind]/[orderId] | Access-checked 60-second signed URLs for source files and reports. |
app/api/stripe/webhook | Marks card orders paid (signature- and amount-checked). Manual payments are confirmed by an admin; see Payments. |
app/api/cron/retention | Scheduled deletion of old documents and reports. |
supabase/migrations/0001_init.sql | Tables, constraints, indexes, RLS, storage buckets and policies. |
lib/local-model/, app/api/model-worker/, tools/local-model-worker/ | Optional local writing model: job queue, worker API and the worker program. See Local writing model. |
Report (automatic): awaiting_payment → paid → screening → completed (or cancelled with credits returned if it fails). The order page calls /api/reports/[id]/advance while open; lib/auto-report/ cuts, reviews and stitches. Older hand-made reports used queued → screening → report_ready → completed.
Refinement: awaiting_payment → paid → queued → under_review → processing → completed (or cancelled)
paid is set only by the verified Stripe webhook or by an admin confirming a manual payment claim. Customers see results only when an order is completed (enforced by RLS, not just the UI).
npm install
cp .env.example .env.local # fill in values
npm run dev
Without Supabase variables the marketing site runs and the app pages show a "not configured" notice.
See .env.example. SUPABASE_SERVICE_ROLE_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET and CRON_SECRET are server-only and must never be prefixed with NEXT_PUBLIC_ or committed. There is intentionally no Turnitin variable.
.env.local.supabase db push (with the Supabase CLI linked), or paste the whole of supabase/setup.sql (all four migrations in order) into the SQL editor and run it once. Step-by-step: docs/SUPABASE_SETUP.md. This creates the private documents and reports buckets with size and MIME limits.<app-url>/auth/callback to redirect URLs.NEXT_PUBLIC_ENABLE_GOOGLE_AUTH=true.update public.profiles set role = 'admin' where email = '[email protected]';
Roles can't be changed from the app or by users (a trigger blocks it)..env.local.<app-url>/api/stripe/webhook for checkout.session.completed and checkout.session.async_payment_succeeded, and copy its signing secret to STRIPE_WEBHOOK_SECRET.stripe listen --forward-to localhost:3000/api/stripe/webhook.ALLOW_DEV_PAYMENTS=true in development to get a "Simulate payment" button. It is ignored in production builds.Stripe is optional. When it isn't configured the card option is simply hidden and customers pay by Alipay, PayMe or bank transfer (see Payments).
npm run dev # development
npm run lint # eslint
npx tsc --noEmit # typecheck (run `npx next typegen` first on a fresh clone)
npm run build # production build
Set all env vars in the project, deploy, point the Stripe webhook at the production URL, and set CRON_SECRET. vercel.json schedules /api/cron/retention daily (Vercel sends Authorization: Bearer $CRON_SECRET).
All prices live in config/pricing.ts (HKD cents) and are recomputed on the server; the browser never sends an amount.
| Product | Price | Limits |
|---|---|---|
AI-Writing Risk Report (combined_screening) | 1 credit per 1,000 characters, minimum 2 credits; first 8,000 of every 20,000 characters charged, the rest free (reportPricing) | 500–100,000 characters, checked in the browser and again in screeningTextOrderInput |
| Daily free report | Free, 1 a day per account, up to 4,000 characters (freeReport) | — |
Characters are counted by billableChars(): spaces count, runs of whitespace count once. ai_screening, similarity_screening and refinement remain in the enum so older orders still display, but they are no longer offered; old refinement orders can't be paid and /services/refinement redirects to the report page.
Customers request a report this way: press Get report (or Paste your text), paste into the dialog, press Enter. The server re-validates, creates an unpaid order holding the text (createScreeningTextOrder), and the browser goes to the order's payment page. At most 10 unpaid orders per account.
The order page offers Alipay, PayMe and bank transfer (FPS), plus card through Stripe Checkout only when STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET are set.
Manual methods are confirmed by a person:
ZP- + the first 8 characters of the order id) to put in the payment note.submitPaymentClaim stores a payment_claims row (amount = the order's server-side price; one pending claim per order). The order stays awaiting_payment; the page shows "Payment submitted — we're confirming it" and refreshes itself every 20 seconds.markOrderPaid path as Stripe (provider = the method, id = the claim id) and queues the order.Alipay orders start first and are checked later (lib/payments/pay-first.ts): when a customer pays one order by Alipay, submitPaymentClaim marks the order paid straight away and the report starts; the claim stays pending and appears in Admin → Overview → Alipay payments to check later, with Received, Not received and All received. Limits: a transaction number that doesn't look like an AlipayHK receipt's (a long number starting with a recent date, lib/payments/alipay-number.ts) waits for Confirm; an account with an unchecked started payment gets the old flow above for its next Alipay order (it waits for Confirm, and starts by itself once the earlier payment is marked Received), and Not received marks the account (audit event payment_claim_fake) so it can't pay orders by Alipay again; the report is not taken back and its payment row is removed. If such a report fails before the check, only the credits used first are refunded; the Alipay part is refunded as credits when the payment is marked Received. Credit top-ups by Alipay and PayPal (and PayMe / bank transfer if enabled) add the credits at once, with any number of unchecked top-ups, and appear under Top-ups to check on the admin home: Received closes them, Not received takes the remaining credits back and marks the account (payment_claim_fake) so its next top-ups wait for a manual Confirm. Reports are moved forward by the customer's open order page; one nobody is watching (page closed, or confirmed hours later) is finished by the every-minute background runner (app/api/cron/chunk-queue, lib/auto-report/sweep.ts).
Customers can never mark an order paid except through that Alipay pay-first path, which is limited as above; otherwise only a verified Stripe webhook or an admin confirming a claim can. Refunds happen outside the app.
Set your payee details before launch in config/payments.ts: replace every value starting with REPLACE, set enabled per method, and put QR images at public/payments/alipay-qr.png and public/payments/payme-qr.png (or set qrImage: null). Missing images show a neutral frame. Admin → Settings lists any method still using placeholders.
lib/notify.ts sends you a message by Telegram and/or email (Resend), after the response so it never slows the customer down:
| Event | When |
|---|---|
order_created | A report or refinement order is created |
payment_submitted | A customer reports an Alipay / PayMe / bank payment (check your account) |
payment_confirmed | You confirm a claim, or Stripe confirms a card payment |
payment_rejected | You reject a claim |
Messages contain the order number, service, word or character count, amount, payment method and account email, never document text, titles or file names. Order and payment messages are retried once if a send fails. If a card payment arrives for an order that was already cancelled, you get a payment_confirmed message telling you to refund it or restore the order.
| Channel | Environment variables |
|---|---|
| Telegram | TELEGRAM_BOT_TOKEN (from @BotFather), TELEGRAM_CHAT_ID |
RESEND_API_KEY, NOTIFY_EMAIL_TO (comma-separated), optional NOTIFY_EMAIL_FROM |
Admin → Settings shows which channels are configured.
Every step writes to audit_events.
The owner's own model, served by Open WebUI on the owner's computer, can write first drafts of refinement orders. Everything is off until MODEL_WORKER_SECRET (24+ characters) is set.
tools/local-model-worker/worker.mjs (Node 18+, no packages) polls POST /api/model-worker/next and reports to POST /api/model-worker/jobs/[id], authenticated with Authorization: Bearer $MODEL_WORKER_SECRET (constant-time check). The owner's computer only makes outgoing requests. Each poll is a heartbeat (model_worker.last_seen_at).0003. claim_model_job hands out one job at a time (for update skip locked), scan feedback first, and requeues a draft whose worker stopped reporting progress for 20 minutes (up to three tries). Service role only.markOrderPaid) or from the admin order page. The worker reads the text from the order at claim time (it is never copied into the job), writes it in chunks of CHUNK_CHARS, and between chunks answers waiting scan feedback. The admin loads the draft into the revision editor and reviews it; the customer sees only what the admin saves and releases. Drafts are deleted with the order's text by the retention cron.lib/local-model/prompts.ts, not in the worker, so the rules ship with the site: clarity, grammar and flow only; keep meaning, citations and voice; nothing about AI detection or evading it.OPENWEBUI_MODEL is a local model; change the page before using a cloud model.Setup (in Traditional Chinese for the owner): tools/local-model-worker/README.md.
Besides the one-a-day allowance per account, free reports are counted per network address (freeReport.perNetworkLimit, lib/network.ts, lib/free-report.ts, migration 0015), so throw-away accounts cannot hand out unlimited reports:
x-forwarded-for entry, else x-real-ip, and only on a host that overwrites them: Vercel (detected from VERCEL), or another host where you set TRUST_PROXY_IP_HEADERS=true because its proxy replaces any value the visitor sends. Elsewhere these headers can be forged, so they are ignored.unknown bucket, so the limit fails closed.GUEST_IP_SALT, or a value derived from the service-role key when unset). Neither the address nor the hash is logged.The old free scan (removed) left some tables behind: scan_results, scan_usage, guest_scan_usage and the SQL functions consume_scan / consume_guest_scan. They are unused; Settings → "Delete scan history" still lets a user erase their own old scan records.
components/motion/interactive-surfaces.tsx mounts one pointer engine (components/motion/pointer-engine.ts) for the whole site: a single requestAnimationFrame loop with spring smoothing that writes CSS variables and nothing else. It lights .glass surfaces where the cursor is, tilts anything with data-tilt, pulls .magnetic buttons, and drives data-depth parallax layers, all through CSS variables. It switches itself off for touch screens and reduced-motion users.
retention.sourceDocumentDays (14) days after an order closes; reports after retention.reportDays (90). This depends on the cron job running with CRON_SECRET set; the admin Settings page shows whether it is.lib/analytics.ts) carry ids and enums only, never document content. No provider is wired yet.Checked in this build:
server-only modules; admin pages and actions call assertAdmin() (role read from the database) before using it.completed.dangerouslySetInnerHTML; redirects restricted to same-site paths.Still needed before production:
awaiting_payment with no upload.AuthorizedTurnitinProvider, only if an officially permitted integration becomes available.