Chat with AI personas of British political figures — past and present — built on demand from their real recorded speeches in Hansard.
Pick a Prime Minister, MP, or Lord — past or present — and have a conversation with an AI version of them grounded in their actual recorded speeches from the official Hansard record of the UK Parliament.
- Browse a curated grid of historical and modern figures, or search 5,000+ members from the live Members API
- Click Chat with X — the persona is built on demand from real speech data
- First-build is cached, so future visitors get instant chat
- Era-locked: Churchill politely refuses to discuss TikTok
Homepage → Profile → click "Chat with X"
│
status check ──┴── cache miss ──▶ Build pipeline (60-120s)
│ │
cache hit SSE stream
│ │
▼ ▼
Chat ◀──────────────── /chat/{slug}
- Fetch — pull the figure's contributions from Hansard
- Modern PMs: server-side filter by
memberId - Historical figures: client-side filter by
AttributedTo+ date range, with parallel topic queries
- Modern PMs: server-side filter by
- Chunk — token-aware split into ~8k pieces (
cl100k_base), with a hard-slice fallback for oversized single sentences - Extract — N parallel LLM calls extract style, vocabulary, rhetorical devices, and verbatim examples per chunk
- Merge — reduce N chunk extractions into one consolidated persona
- Render — write Markdown system prompt + JSON examples bank
- Cache — Vercel Blob in production, filesystem in dev
User message → /api/chat → server loads persona's .md + 8 randomly-sampled
verbatim quotes → builds the system prompt → streams Groq Llama in the persona's
voice via the Vercel AI SDK.
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router, Turbopack) |
| UI | React 19 · Tailwind CSS v4 · shadcn/ui · Lucide |
| Streaming chat | Vercel AI SDK v6 (useChat, streamText) |
| LLM (offline build) | OpenRouter — fallback chain across 5 free models |
| LLM (chat) | Groq — Llama 3.3 70B / 3.1 8B Instant |
| Validation | Zod schemas on every external API response |
| Storage | Vercel Blob (prod) · filesystem (dev) |
| Data sources | UK Parliament Members API + Hansard API (no key required) |
| Tokeniser | js-tiktoken (cl100k_base) |
Free OpenRouter models are 1 RPM each per upstream provider. The pipeline tries
each model in OPENROUTER_MODELS in order; on 429, parse error, or
"Provider returned error", it rolls forward to the next model. Five different
upstream providers gives ~5 RPM cumulative with one API key.
OPENROUTER_MODELS = nemotron, qwen3, gpt-oss, gemma-4, minimax-m2.5
↓ Nvidia ↓ Venice ↓ OpenAI ↓ Google ↓ MiniMax
───────────────────────────────────────────────
5 independent rate-limit pools
git clone https://github.com/pradhankukiran/statesmen-ai
cd statesmen-ai
npm install
cp .env.example .env.local # then fill in your keys
npm run dev # localhost:3000| Variable | Required | Purpose |
|---|---|---|
OPENROUTER_API_KEY |
✅ | Persona build (extract + merge calls) |
GROQ_API_KEY |
✅ | Streaming chat |
OPENROUTER_MODELS |
recommended | Comma-separated fallback model list |
OPENROUTER_EXTRACT_MODELS |
optional | Override extract stage only |
OPENROUTER_MERGE_MODELS |
optional | Override merge stage only |
GROQ_CHAT_MODEL |
optional | Single chat model id |
BLOB_READ_WRITE_TOKEN |
prod only | Vercel Blob (auto-injected when you create a Blob store) |
Get keys: OpenRouter · Groq
npm run script:fetch # smoke-test Hansard + Members APIs
npm run script:extract # chunk + run one extraction call
npm run script:build # full pipeline for Boris Johnson
npm run script:build -- --thatcher # historical (attribution-mode) buildstatesmen-ai/
├── app/
│ ├── page.tsx # homepage: hero + search + popular grid
│ ├── p/[id]/ # profile (numeric id or slug)
│ ├── build/[slug]/ # SSE build progress page
│ ├── chat/[slug]/ # streaming chat
│ └── api/
│ ├── persons/{search,[id]}
│ ├── persona/{status,build}
│ └── chat
├── components/
│ ├── ui/ # shadcn primitives (Base UI under the hood)
│ ├── chat-window.tsx # useChat + memoised markdown bubble
│ ├── build-progress.tsx # SSE consumer with live progress
│ ├── chat-cta.tsx # status-check + route to chat or build
│ ├── search-bar.tsx # debounced + abort-controlled search
│ ├── person-card.tsx
│ ├── person-grid.tsx
│ └── site-{header,footer}.tsx
├── lib/
│ ├── persona.ts # build orchestrator + renderers
│ ├── extractor.ts # per-chunk LLM extraction
│ ├── merger.ts # consolidate N chunks → 1 persona
│ ├── chunker.ts # token-aware splitter
│ ├── cache.ts # Blob + FS dual backend
│ ├── hansard.ts # Hansard API client
│ ├── members.ts # Members API client
│ ├── models.ts # model fallback resolution
│ ├── popular.ts # popular-pms registry loader
│ ├── slug.ts # name → cache key
│ └── prompts/{extract,merge}.ts
├── data/popular-pms.json # curated featured figures
└── scripts/ # CLI smoke tests
Minimal shadcn × hint of neo-brutalism, single yellow accent.
- Sharp
rounded-mdedges;rounded-noneonly for brand pills border-2everywhere a border reads- Brand yellow rationed: focus rings, primary CTAs, hero pills, accent stripes
- Confident type —
text-5xltotext-7xlheadlines,text-basetotext-lgbody - Generous whitespace, no shadow circus, no gradients
- Push the repo to GitHub
- Import the project on Vercel
- Paste the env vars from your
.env.local - Storage tab → Create Blob store → connect to project → done
The Blob token is injected automatically; no copy-paste needed.
- UK Parliament Hansard — open access to the official record
- UK Parliament Members API — member metadata + portraits
- OpenRouter + Groq — LLM inference
- Vercel AI SDK, shadcn/ui, Lucide — building blocks
Statesmen AI generates conversational AI personas from real public speeches. Responses are AI-generated and do not represent actual statements by the people depicted. This is a personal engineering project, not a journalistic or political tool.
Built as a personal project · powered by free-tier infrastructure · open to fork