Lexiverse
Lexiverse - AI contextual reading engine for English learners. Your next article is your next review.
Learn the English chunks native speakers use — one chunk a day, out loud.
Translation pending
EducationTypeScript · ShellGitHub @gusanchefullstackOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Learn real English chunks daily with self-hosted open-source pronunciation scoring
Loading the full README.
Read the full README ↗A web app that teaches English chunks — the ready-made phrases native speakers use, like “that makes sense” or “get used to” — to learners from A1 to C2, with one daily session: hear it, say it twice, write it, read it aloud, and get pronunciation scores and friendly tips from open-source AI running on infrastructure we control.
Live: https://englishchunks.guru
Most apps teach single words, but fluent speakers think in chunks: “by the way”, “make a decision”, “I tend to…”. Learners need to say those chunks out loud and know exactly which sounds to fix — and pronunciation feedback usually means sending your voice to a closed cloud API.
This project does the speech scoring with self-hosted open models (Silero VAD, NVIDIA Parakeet, wav2vec2 phonemes) and writes tips with an open-weight LLM (Gemma 4). Learner recordings are scored in memory and deleted immediately — they never touch a disk or a third party. Pronunciation hints come from a fixed, curated table of ~40 English sounds; the AI only adds warmth around them, so the teaching is always correct and consistent.
| Landing | Daily session (Learn step) |
|---|---|
Browser (Next.js 16, React 19) ── Server Actions / Route Handlers (TypeScript) ── Neon Postgres (Drizzle)
│ MediaRecorder (WebM/Opus or MP4/AAC) │
│ audio in memory ───────────────────────────▶ │ ── HMAC-signed HTTPS ──▶ Scoring service (FastAPI, Python 3.12)
│ │ Silero VAD → Parakeet TDT 0.6B v2 (int8)
│ │ → wav2vec2 espeak phonemes (int8, CTC + GOP)
│ │ → 0–100 score + per-sound results
│ │ ◀── weakest sounds ──── curated IPA hint table (deterministic)
│ │ ── Gemma 4 26B A4B (Vercel AI Gateway) → warm tip, writing grades
│ ▶ play ◀── 302 ── /api/tts?ref=… ────────── │ ── ElevenLabs Flash (lazy, cached in DigitalOcean Spaces)
│ │ ── Resend (verification, reminders) · Stripe Billing (off until admin activates)
A session is Learn → Speak (try 1, try 2) → Write → Read aloud → Daily score, with streaks, points, a level-band ranking, a searchable chunk library, learner chunk suggestions with admin review, and an admin console.
Diagrams of the physical deployment, logical components and user workflow are in docs/architecture.md.
Prerequisites
| Tool | Version |
|---|---|
| Node.js | ≥ 22 |
| pnpm | 10 |
| Python | 3.12 (installed by uv) |
| uv | ≥ 0.12 |
| espeak-ng | ≥ 1.51 |
| Docker | any recent (local Postgres) |
git clone https://github.com/gusanchefullstack/englishchunks.guru.git
cd englishchunks.guru
pnpm install
# Local Postgres (localhost URLs use the standard `pg` driver)
docker run -d --name ec-pg-dev -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=englishchunks \
-p 54329:5432 postgres:17-alpine
cp web/.env.example web/.env.local # fill in values (never commit them)
export DATABASE_URL=postgres://postgres:dev@localhost:54329/englishchunks
pnpm --filter web db:migrate
pnpm --filter web db:seed # voices, sound hints, 30 starter chunks
# Scoring service + models (~950 MB of ONNX int8 models)
cd scoring
cp .env.example .env
uv sync
uv run python -m scoring.models.download # Silero VAD + Parakeet
uv run --extra export python -m scoring.models.download --wav2vec2 # export + quantize wav2vec2
# Terminal 1 — scoring service on :8001 (models preload in the background)
cd scoring && uv run uvicorn scoring.app:app --port 8001
curl localhost:8001/healthz # {"status":"ok","models":{"vad":true,"asr":true,"phonemes":true}}
# Terminal 2 — web app on :3001
pnpm --filter web dev
Open http://localhost:3001, create an account (confirm you're 16+), click the link in the verification email, choose your level, and start today's chunk.
web/.env.local (see web/.env.example):
| Variable | Description | Required | Default |
|---|---|---|---|
DATABASE_URL | Postgres URL (Neon in production, local Postgres in dev) | Yes | — |
BETTER_AUTH_SECRET | Session signing secret (≥ 32 chars) | Yes | — |
BETTER_AUTH_URL | Public base URL of the app | Yes | — |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | Google sign-in | No | disabled |
AI_GATEWAY_API_KEY | Vercel AI Gateway key | Yes | — |
AI_FEEDBACK_MODEL | Tips + writing grades | No | google/gemma-4-26b-a4b-it |
AI_AUDIT_MODEL | Independent chunk auditor | No | openai/gpt-oss-20b |
ELEVENLABS_API_KEY / ELEVENLABS_MODEL | Text-to-speech | Yes / No | — / eleven_flash_v2_5 |
SPACES_ENDPOINT, SPACES_REGION, SPACES_BUCKET, SPACES_KEY, SPACES_SECRET, SPACES_CDN_URL | DigitalOcean Spaces (TTS cache, avatars). Also needed at build time for the CSP | Yes | — |
RESEND_API_KEY / EMAIL_FROM | Transactional email | Yes | — |
SCORING_URL / SCORING_SHARED_SECRET | Scoring service URL and HMAC secret (≥ 32 chars) | Yes | — |
CRON_SECRET | Bearer secret for /api/cron/* | Yes | — |
UNSUBSCRIBE_SECRET | Signs one-click unsubscribe links | Yes | — |
STRIPE_SECRET_KEY | Stripe restricted key (rk_…) | When billing is on | — |
scoring/.env: SCORING_SHARED_SECRET (same as the web app), MODELS_DIR (default ./models), MAX_AUDIO_SECONDS (15), MAX_AUDIO_BYTES (1 MiB), SOUND_HINTS_PATH (set in Docker).
Full contracts live in specs/001-chunk-learning-app/contracts/. Highlights:
POST /api/sessions/{id}/attempts (web)Multipart: audio, step (speak_1 · speak_2 · read_aloud), item, mimeType.
{
"kind": "scored",
"score": 82,
"components": { "sounds": 83, "words": 100, "flow": 95 },
"weakestSounds": [{ "ipa": "ð", "hint": "Put your tongue tip lightly between your teeth, then use your voice. Not \"d\" or \"z\".", "exampleWords": ["that", "the", "other"] }],
"tip": "Lovely rhythm! Let your tongue touch your teeth on “that”.",
"improvement": 12
}
422 {"kind":"unusable","reason":"no_speech"} asks the learner to try again (not counted).
POST /v1/score (scoring service, HMAC-signed)Headers: X-Timestamp, X-Request-Id, X-Signature: sha256=HMAC(secret, "<ts>.<id>"). Multipart: audio, mime_type, expected_text, level.
{ "score": 81, "components": { "phoneme_accuracy": 81, "word_accuracy": 67, "fluency": 100 },
"sounds": [{ "ipa": "ð", "score": 2, "count": 1 }], "weakest_sounds": ["ð", "æ"],
"transcript": "That makes sense.", "model_versions": { "asr": "parakeet-tdt-0.6b-v2-int8" } }
Other routes: GET /api/tts?ref=chunk:<id>|example:<id>|sentence:<id> (302 to audio), POST /api/profile/avatar, POST /api/billing/checkout, POST /api/billing/portal, POST /api/webhooks/stripe, POST /api/cron/hourly, GET|POST /api/cron/daily, POST /api/unsubscribe (RFC 8058).
englishchunks.guru/
├── web/ # Next.js 16 app (UI + TypeScript backend) — Vercel
│ ├── src/app/ # routes: (marketing), (auth), (learner), onboarding, admin, api
│ ├── src/components/ # session steps, progress, library, admin, shadcn/ui
│ ├── src/lib/domain/ # session engine, scoring→feedback, limits, ranking, billing…
│ ├── src/lib/providers/ # LLM, TTS, storage, email, scoring client, Stripe (swappable)
│ ├── src/content/ # curated IPA sound-hint table (single source of truth)
│ ├── src/db/ # Drizzle schema, migrations, seeds
│ ├── scripts/ # library seeding pipeline, latency check
│ └── tests/ # unit, integration (PGlite + MSW), contract, e2e (Playwright + axe)
├── scoring/ # Python 3.12 FastAPI speech scoring — DigitalOcean Droplet
│ ├── src/scoring/ # audio decode, engines, CTC alignment/GOP, combination, app
│ ├── tests/ # unit, contract, no-audio-persisted, calibration (opt-in)
│ └── deploy/ # Dockerfile, systemd units, hourly cron timer, Caddy
├── specs/001-chunk-learning-app/ # spec, plan, research, data model, contracts, tasks (Spec Kit)
└── .specify/memory/constitution.md # project principles (open-source AI, privacy, tests…)
| Suite | Tool | Command |
|---|---|---|
| Web unit + integration (real SQL in PGlite, external services mocked with MSW — unmocked network fails the test) | Vitest | pnpm test |
| End-to-end (fake mic, real app + Postgres, deterministic AI fakes, axe WCAG 2.2 AA scans, keyboard-only run) | Playwright | pnpm --filter web test:e2e |
| Scoring unit + OpenAPI contract + "no audio persisted" | pytest | cd scoring && uv run pytest |
| Scoring calibration on speechocean762 (SC-005, ≥ 85 %) | pytest | uv run pytest -m calibration |
| Scoring latency (SC-003, p95 ≤ 5 s) | — | uv run python -m scoring.bench |
| Lint · types · secrets | ESLint · tsc · ruff · mypy · gitleaks | pnpm lint && pnpm typecheck && pnpm secret-scan |
Current results: 197 web tests, 21 e2e tests, 30 scoring tests; calibration 89.1 % over 66,189 pairs; scoring server p95 1.68 s on the production Droplet (see scoring/BENCHMARKS.md).
Every change goes through a pull request to main; CI (lint, types, unit/integration, Playwright e2e with axe, scoring tests, secret scan) must pass, and merging deploys production automatically. The scoring service is updated separately per its runbook.
vercel.json schedules the daily cron.docker build -f scoring/deploy/Dockerfile -t ec-scoring ., mount models read-only, run with scoring/deploy/scoring.service (read-only root, tmpfs /tmp with exec for espeak), Caddy for TLS, and cron-hourly.timer to call /api/cron/hourly (reminders, invite expiry). See scoring/deploy/RUNBOOK.md.ACTIVATE in Admin → Settings, after the go-live checklist (Stripe test-mode demo, Vercel Pro, restricted live keys, Stripe Tax decision)./speckit-analyze caught real gaps (Google account-linking takeover, overdue-payment limits) before any code existed.cookies(), headers(), searchParams, DB reads) must live inside <Suspense>; read headers before touching anything that needs env during prerender; await connection() marks DB reads as request-time; a streamed page can't change its status code later, so real 404s for the admin area come from proxy.ts.--tmpfs is noexec by default, which breaks phonemizer's espeak-ng copy.before hook; implicit Google account linking had to be disabled.onUnhandledRequest to onUnhandledFrame (the old option silently let tests hit the real network); a real Chrome MediaRecorder recording and the Docker image each exposed bugs that every fake had hidden.<body> — use aria-disabled/aria-busy instead; brand colors needed an AA-safe palette and validated chart colors.feat/<short-name> or fix/<short-name>..specify/memory/constitution.md — open-source AI at the core, deterministic pedagogy, tests first, privacy.pnpm lint && pnpm typecheck && pnpm test, the e2e suite and uv run pytest green.feat:, fix:, docs:…) and open a PR describing the user-visible change.Distributed under the MIT License. See LICENSE for details. The AI models keep their own licenses: Silero VAD (MIT), Parakeet TDT 0.6B v2 (CC-BY-4.0), wav2vec2-lv-60-espeak-cv-ft (Apache-2.0), speechocean762 (CC-BY-4.0, calibration only).
Gemma · NVIDIA Parakeet via sherpa-onnx · wav2vec2 espeak · Silero VAD · espeak-ng · speechocean762 · Next.js · Better Auth · Drizzle · shadcn/ui · Vercel AI SDK · ElevenLabs · Resend · Stripe · Spec Kit. Palette from coolors.
Gustavo Sanchez — gustavosanchez.dev
STRIPE_WEBHOOK_SECRET | Webhook signing secret | When billing is on | — |
STRIPE_PRICE_BASIC / _STANDARD / _PREMIUM | One Price per plan Product | When billing is on | — |
NEXT_PUBLIC_LANDING_VIDEO_URL / _CAPTIONS / _POSTER | Landing video (Spaces CDN) + WebVTT captions and poster (served from web/public/video) | No | walkthrough fallback |
cursor: pointer, so a single global hover layer (pointer, deeper primary, muted fill) beat editing 30+ class strings.