Introduction
AI Concepts: how the lessons are built
AI Concepts is bite-sized, interactive lessons — a few sentences, a worked example, then questions. Hearts, XP and a streak, in the Duolingo mould. This page is about how it's put together and the rules its content has to follow.
Chapter 1 is tokenisation, and it runs the real o200k_base vocabulary (gpt-tokenizer) in the browser. Every token count the app shows — in a lesson, in a question, in the playground — is the genuine one, not an approximation. That covers the surprising splits, the cost of writing in a language the tokeniser saw little of, the tokens-per-word ratio, and the byte-level fallback that means no text is ever "unknown".
Chapter 2 is embeddings, built on a small hand-made map of meaning, and chapter 3 is attention, built on hand-authored attention patterns. Those two are simplifications, and they say so on every screen — see Honesty about the map.
Chapter 4 is output logits, and it goes back to real maths: the starting scores are illustrative, but the softmax, temperature scaling, sampling, cross-entropy loss and perplexity are all the actual formulas computed live (web/lib/ai-concepts/logits.ts).
Chapter 5 is context windows, and its counts are real too — the budget and overflow screens run the same tokeniser as chapter 1 over the text on screen. The window sizes are deliberately tiny (512 and 40 tokens) so the effects fit on a screen; the chapter says so on every screen.
Where the code lives
The lessons are part of the web app — no separate install, no backend, no API keys. The tokeniser is a local vocabulary file, so everything runs client-side.
| Path | What's in it |
|---|---|
web/app/ai-concepts/ | The routes |
web/lib/ai-concepts/ | Tokeniser, embeddings, attention, logits, grading, progress |
web/lib/ai-concepts/chapters/ | The content |
web/components/ai-concepts/ | The lesson engine and screen components |
Structure
Chapter → lesson → screen. A chapter is a topic; a lesson is one sitting of a handful of screens ending in a recap; a screen is a single beat. The engine in components/ai-concepts/Lesson.tsx runs one lesson: it renders screens, tracks hearts and XP, and decides what counts as correct.
All of it is data — adding a concept means authoring content, not writing UI.
Lessons must be self-contained
Every lesson has its own URL and is meant to be shared, so someone can arrive at lesson 3 having done nothing else. Each lesson therefore opens by establishing what it needs in a sentence or two, and no lesson leans on "as you saw earlier" to carry an idea. Cross-references are fine as flavour; they must not be load-bearing.
Deep links
Each lesson is its own route, prerendered at build time with its own title and description:
/ai-concepts the lesson map
/ai-concepts/tokenisation/case-digits-spaces straight into one lesson
/ai-concepts/tokenisation redirects to the chapter's first lesson
A lesson id is a URL slug. Once shared, it shouldn't change — links to it will exist. An unknown chapter or lesson is a 404.
Every lesson row has a Copy link button, and the completion screen offers one too.
| Screen kind | What it does | Scored |
|---|---|---|
teach | Eyebrow, title, copy, optional token strips | No |
tapReveal | Tap a specimen to watch it split, then a takeaway | No |
choice | Authored multiple choice, for conceptual questions | Yes |
compare | "Which costs more tokens?" between two strings | Yes |
count | "How many tokens?" with generated near-miss options | Yes |
playground | Free typing against a target count | Yes |
ratio | A passage with words / tokens / ratio worked out live | No |
vectorReveal | Tap a word to see the numbers behind it | No |
semanticMap | The meaning map, free to explore | No |
cosineView | Tap two words, see the angle and cosine between them | No |
attentionView | Tap a word in a sentence, see what it leans on | No |
softmaxView | Logits → probabilities, with an optional temperature dial | No |
sampleRun | Draw tokens at random and tally them against the odds | No |
budget | What's filling a context window, counted for real | No |
overflow | Add chat turns until the earliest fall out | No |
perplexityView | Per-token loss and perplexity over a sequence | No |
analogyDemo | Teach screen that draws arrows between word pairs | No |
resolve | "Which word does X lean on?" | Yes |
pairCount | "How many word-to-word links?" — n², computed | Yes |
likeliest | "Which token comes next?" — top logit, computed | Yes |
nearest | "Which sits closest to X?" — metric: 'distance' | 'cosine' | Yes |
odd | "Which one doesn't belong?" | Yes |
analogy | "a is to b as c is to ___" | Yes |
recap | Closing summary and XP | No |
Correct answers are worth 10 XP; a wrong one costs a heart, and three lost hearts ends the run.
Answers are computed, never authored
compare, count and playground don't store an answer — the tokeniser is the source of truth at runtime (lib/ai-concepts/grade.ts), so a question can't drift out of sync with the vocabulary. nearest, odd and analogy work the same way against the meaning map, so a question can never contradict the picture on screen.
choice is the only exception — it's for conceptual questions with no measurable answer, so its answer index is authored.
Set showCounts: true on a choice screen only when its options are specimens of text. On a conceptual question, annotating each option with its own token count is noise.
Adding a chapter or lesson
- Write
lib/ai-concepts/chapters/<name>.tsexporting aChapterwith one or more lessons. - Register it in
lib/ai-concepts/chapters/index.tsand drop it from theupcominglist. - If it needs a screen type that doesn't exist, add it to
lib/ai-concepts/types.ts,grade.ts,components/ai-concepts/Screens.tsxand theLessonswitch.
New lessons are picked up automatically — the routes, the sitemap and the lesson map all derive from the chapters array.
Aim for roughly 5–7 screens per lesson, ending in a recap, with at least two scored screens so there's an XP payoff. If a lesson creeps past ~8 screens it wants splitting.
Chapter.source is required, so a chapter can't ship without saying where its numbers come from. It renders as a small line with a link on every screen of the lesson — not on a home screen the learner passes through once. Chapter 1 names the vocabulary and links to tiktoken; chapter 2 admits its map is hand-built.
Prose that quotes a specific split is a claim about the vocabulary. Verify it before shipping rather than trusting intuition — strawberry is three tokens (st + raw + berry), which almost nobody guesses:
node -e "import('gpt-tokenizer/encoding/o200k_base').then(({encode,decode})=>\
{const t=encode(process.argv[1]);console.log(t.length,t.map(i=>JSON.stringify(decode([i]))).join(' '))})" 'your text'
Any screen that states a count also has to say which vocabulary it came from. TokenStrip prints N tokens · o200k_base underneath whenever it shows IDs, and takes a source prop to force it on elsewhere. Without that, a learner checking a number against a different model — or against a chatbot, which can't see its own tokens and will simply guess — reasonably concludes the app is wrong. Chapter 1 makes the same point in a dedicated screen.
Honesty about the map
Chapter 1 can promise real data because a tokeniser is a small local file. Embeddings are not: a real one is learned from enormous amounts of text and has hundreds of dimensions, and fetching one would mean an API key and a backend — which these lessons deliberately don't have.
So lib/ai-concepts/embeddings.ts is a hand-placed 2-D map of 16 words. Two rules keep that from becoming a lie:
- The app never presents it as model output. The map screen says it's hand-built and 2-D, the home screen repeats it, and the
vectorRevealscreen shows the word's two real coordinates rather than inventing a realistic-looking 768-number vector. - Everything measured is measured for real. Nearest words, odd-one-out and the analogy are all computed from the coordinates. Nothing is authored, so no question can disagree with the map the learner is looking at.
The royalty cluster is laid out as an exact parallelogram, which is why king - man + woman lands precisely on queen.
Distance vs cosine
The map's nearest-neighbour questions use Euclidean distance, because that's what a 2-D picture makes legible and it's how projected embedding plots are read in practice. But real embedding similarity is cosine — the angle between vectors — so the chapter teaches that explicitly rather than leaving distance as the takeaway.
cosine() measures the angle from CENTRE (the middle of the map), not from the top-left corner: direction is only meaningful relative to the middle of the space. Distance and cosine happen to agree on every graded question in the chapter, which was checked — so introducing cosine doesn't contradict anything taught earlier.
Upgrade path: to make this real, embed a word list once with a real model in a throwaway script, commit the resulting vectors as JSON, and swap WORDS for it — project the vectors to 2-D for drawing but run the maths on the full vectors. The screen types and grading wouldn't change.
The same applies to attention
lib/ai-concepts/attention.ts holds hand-authored patterns for two sentences, written as a sparse "which words does this one pull on" map and normalised into rows that sum to 1, the way a real attention row does after its softmax. Same two rules: the chapter never claims they came from a model, and every graded answer (resolve, pairCount) is computed from the numbers on screen.
The two sentences differ by one word — tired vs wide — which is what makes "it" resolve to animal in one and street in the other. That flip is the point of the chapter, so if you edit the links, check both still resolve the way the copy claims.
Design
Letterpress: tokens are sorts set in a composing stick, each stamped with its vocabulary ID. Leading spaces are drawn as a baseline underscore, because "the space travels with the word" is one of the things the chapter teaches — it has to be visible. Fraunces for display, Public Sans for body, IBM Plex Mono for anything that is literally text-as-data.
Typography is pending
The section's own typefaces haven't been wired up in web yet — font-display and font-mono currently fall back to the site's Geist. The letterpress palette (paper, ink, lead, rule, brass, press) is live in web/app/globals.css.
Notes
- Progress (XP, streak, best score per lesson) is in
localStorageunderai-concepts:progress:v2, keyedchapterId/lessonId. There is no account or backend. v1 stored chapter-level scores and is ignored, so anyone who used the earlier build starts fresh. - Because
localStoragedoesn't exist during prerendering, progress is read throughlib/ai-concepts/use-progress.ts— the server and the hydration pass see zero, and real values arrive on the first commit in the browser. - The lesson route's client bundle is ~2 MB because it embeds the o200k vocabulary. That's the cost of real counts, and it's scoped to
/ai-concepts— no other page on the site loads it. Lazy-load it if a chapter ever ships that doesn't need the tokeniser. - Keyboard:
1–4pick an answer,Enterchecks and continues.