Lexiverse
Lexiverse - 영어 학습자를 위한 AI 기반 문맥 독해 엔진
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Monkeytype, but Singaporean.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A Singlish typing test in the style of monkeytype. Pick a mode and a duration, type the prompt, get your wpm and accuracy. Dark, minimal, no accounts — just wpm lah. An optional global leaderboard (a small FastAPI + SQLite backend) lets you post a nickname and score; the core test still runs fully client-side without it.
Live modes:
lah, shiok, kopi, paynow, bto, hdb, …)ang mo kio — north south line)gd nite swt drms — msn status)The results screen shows a pace-over-time graph (running wpm per second, with an × on every second that contained a wrong keystroke) and the singlish you typed — each word from the run, with a hover tooltip giving its meaning and a click-through to its dictionary entry (Wiktionary, or Wikipedia for the institutions).
output: "export") — no server, no API routes (the optional
leaderboard is a separate backend in leaderboard-api/)lib/ (wpm/accuracy, pace, challenges, corpora, records)mainnpm install
npm run dev # dev server at http://localhost:3000
Other scripts:
npm run build # production build -> static export in ./out
npm test # unit tests for lib/ (wpm math, pace, challenges, corpora, records)
To preview the exact static output Vercel serves:
npm run build
npx serve out # or: python3 -m http.server -d out 8000
The leaderboard talks to a separate backend. Point the frontend at it with an env var (defaults to the production API when unset):
# .env.local
NEXT_PUBLIC_LEADERBOARD_API=http://localhost:8000
Run the backend locally from leaderboard-api/:
pip install -r leaderboard-api/requirements.txt
KOPITYPE_DB=./leaderboard.db uvicorn main:app --port 8000 # from leaderboard-api/
KOPITYPE_DB sets the SQLite path (default /data/leaderboard.db, meant for a
mounted volume in the container). The board shows each player's best run per mode +
duration, so multiple attempts don't crowd it out.
The API accepts known corpus IDs and 15/30/60-second runs, rejects blank names,
and caps raw WPM at 1,000. SQLite enforces 10 submissions per client per minute
and 200 total per minute across workers sharing the database. Expired counters
are removed during submissions. CORS defaults to kopitype.com, www.kopitype.com
and localhost:3000; override CORS_ORIGINS with exact comma-separated origins.
IP quotas use the ASGI peer address, never arbitrary forwarded headers. Configure Uvicorn's trusted proxy IPs only for your actual reverse proxy; otherwise users behind a proxy share its quota. A shared NAT also shares a quota. CORS and quotas reduce browser misuse and spam; scores remain client-reported, are not verified proof of a typing run, and nicknames are not authenticated identities. Strong anti-cheat requires server-issued challenges and run validation as a follow-up.
When the API runs in Docker behind a reverse proxy on the same host (nginx →
127.0.0.1:8124), every request reaches the container from the Docker bridge
gateway (typically 172.17.0.1), so without extra config all players share one
10-per-minute quota. Tell Uvicorn to trust that hop's X-Forwarded-For with
its FORWARDED_ALLOW_IPS env var, and publish the port on loopback only so
nobody can reach the container directly and forge the header:
docker run -d --name kopitype-leaderboard \
-p 127.0.0.1:8124:8124 \
-e FORWARDED_ALLOW_IPS=172.17.0.1 \
-v "$PWD/data:/data" kopitype-leaderboard
Backend regressions: install leaderboard-api/requirements.txt plus httpx, then
run cd leaderboard-api && python -m unittest test_api.
The whole test lives on one page (app/page.tsx → components/TypingTest.tsx), a small
state machine: idle → running → finished.
<input> captures typing. Its value is the current word's buffer, so it
works with both physical keyboards and mobile soft keyboards (we read value changes,
not keycodes). A space commits the word. Backspace erases within the current word,
and — monkeytype-style — crosses back into the previous word only if that word was
committed with an error; correctly typed words are locked.components/WordStream.tsx renders per-character state: pending / correct /
incorrect / extra, with a caret, and scrolls as you descend the lines.lib/wpm.ts holds the pure, unit-tested math: wpm = (correct chars / 5) / minutes,
plus raw wpm and accuracy.components/Stats.tsx shows the result, your
previous score, and your personal best (per mode + duration, in localStorage —
still no accounts). Tab (while typing or on the results screen) or the restart
button starts a fresh test; Shift+Tab and Escape are never hijacked, so
keyboard navigation always works.lib/sound.ts (WebAudio, no
audio assets) behind a "sound" toggle that's remembered in localStorage.lib/pace.ts, pure + tested); the results screen renders them as an SVG line
chart (components/PaceChart.tsx — hover crosshair, keyboard arrows, no chart
library).data/glossary.json maps terms to short meanings (plus an optional link);
components/GlossaryWords.tsx turns the words you attempted into hoverable,
clickable chips.Click custom in the mode bar, type a phrase (up to 300 chars), share the link.
The phrase travels base64url-encoded in the URL fragment (#c=…) — no backend, no
storage, nothing expires. Opening a challenge link loads a fixed-phrase test that
ends when the phrase does; the timer counts up and the wpm is computed from elapsed
time. Challenges don't touch personal bests. lib/challenge.ts owns the
encode/decode (validated + normalized on the way in — never trust a URL).
Click submit in the mode bar to send in words, quotes, or meanings. With no
backend, the review queue is the repo itself: the form opens a prefilled GitHub
issue (labelled corpus-submission) containing a ready-to-paste JSON snippet.
Nothing goes live until a maintainer vets it and commits it into data/ —
crowdsourced input, curated output.
This is deliberately a two-step change (say you want a "kopi" mode):
Drop a JSON file in data/:
["kopi", "teh", "milo", "kosong"]
{ "text": string, "source": string }:
[{ "text": "kopi c kosong gau one cup dabao", "source": "kopi order" }]
Register it in the one MODES array in lib/corpus.ts:
import kopi from "../data/kopi.json";
export const MODES: ModeDef[] = [
// ...existing modes...
{ id: "kopi", label: "kopi", type: "words", words: kopi as string[] },
];
That's it — it shows up in the mode bar automatically. Add gated: true to make a mode ask
for a one-tap confirm before it switches in (that's how vulgar.json is wired). Word-mode tokens must
be single words (no spaces); put any multi-word terms in a quotes corpus instead.
| File | Entries | Shape |
|---|---|---|
data/words.json | ~245 | single lowercase tokens |
data/sg.json | ~97 | single lowercase tokens (everyday SG) |
data/quotes.json | 72 | { text, source } phrases |
data/mrt.json | ~122 | { text, source } station → line |
data/xmm.json | 33 | { text, source } vowel-less texts |
data/vulgar.json | 44 | single tokens, gated |
data/glossary.json | ~217 | term: { meaning, link? } for the results-screen glossary |
The corpora are hand-written and meant to be human-readable (one entry per line) so they're easy to vet and extend — which is also how crowdsourced submissions land (see above).
Accounts, themes, multiplayer, punctuation/numbers toggles, i18n. (Personal bests, sound, the pace graph, custom challenges, and the glossary all landed post-v1 — local-only or in-URL. The global leaderboard came later and is the one piece with a backend; everything else still runs without one.)