Lexiverse
Lexiverse - 영어 학습자를 위한 AI 기반 문맥 독해 엔진
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Learn real English chunks daily with self-hosted open-source pronunciation scoring
README 전체를 불러오는 중입니다.
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.