Rework the home page into a single landing surface and give the app a
consistent visual language, chosen as "illuminated tome" over two other
pitched directions because it builds on the existing Cinzel + gold identity
instead of replacing it.
Home is now Continue (up to 4 in-progress stories, each showing where you
left off) over a scenario shelf, each section with a "See all" link. The
full adventure list moves to /adventures.
Scenario cover art has three tiers, in precedence order: an uploaded picture
(downscaled client-side to 400px WebP before storing), an emoji, or gradient
art generated from a hash of the title so no card is ever an empty box.
Adventures inherit their scenario's art. Images live in the row rather than
on disk because Render's free tier has no persistent volume, and it keeps
export bundles self-contained; list responses carry a cacheable
/api/scenarios/{id}/image URL rather than the base64.
Also: ambient drifting motes behind the app, loading skeletons, staggered
card entrance, ornamental scene breaks and a drop cap in the story, a
"Weaving" thinking indicator, and a toast system replacing every alert().
Two fixes found along the way:
- Importing a scenario bundle with no "tags" key returned a 500. Column
defaults are not applied until flush, so the attribute was still None
when the width clamp sliced it.
- Anything meaning "the story's latest narration" was missing action type
"start", which is the only text a freshly created adventure has, so new
adventures looked empty. Collected as NARRATION_TYPES.
Migrations 30 and 31 add scenarios.image and scenarios.icon; both are
additive with a '' default and were verified against a database stamped at
29. vite.config.js now reads AIDND_API_PORT so the recurring port-8000
clash with another local app needs no file edit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FGY1yvzSeKgTtRfeVtDmx
AI D&D
An AI Dungeon-style interactive storytelling app you can run entirely on your own machine — with your own AI model. Create scenarios, play open-ended adventures where an LLM narrates the world, and extend the engine with JavaScript scripts compatible with real AI Dungeon scripting.
▶️ Try it live: ai-dnd-1gmp.onrender.com
Play a demo scenario as a guest — no sign-up, no API key needed. (Hosted on Render's free tier, so the first load after it's been idle takes ~30–60s to wake up.)
Built with FastAPI + SQLite on the backend and React (Vite) on the frontend. Works with any OpenAI-compatible endpoint: Ollama and LM Studio locally, or OpenRouter / OpenAI / Groq / vLLM in the cloud — endpoint, key, and model are all runtime settings, and OpenRouter's free-tier models make the whole experience $0.
📸 Screenshots and a demo GIF are coming; for now the fastest tour is running it — one command with Docker.
Features
- The full play loop — Do / Say / Story / Continue actions, streamed AI responses (SSE), retry, undo, and edit. Reasoning models supported: "thinking" streams into a collapsible 💭 panel with its own token budget.
- AI Dungeon-compatible context engine — memory, author's note, and story cards (world
info) triggered by keywords in recent story text, assembled under a token budget
(
backend/app/context/builder.py). - Insights: total prompt transparency — every turn stores the exact prompt sent to the model; open 🔍 on any AI action to see each context component and why it was included.
- JavaScript scripting, AI Dungeon-compatible —
onInput/onModelContext/onOutputmodifiers with sharedstateand aworldEntriesAPI, executed in an embedded quickjs sandbox (backend/app/scripting/). Real AI Dungeon scripts import and run. In-app CodeMirror editor included. - Auto-summarization + Memory Bank — the modern AI Dungeon memory system: AI-generated
memories every few actions, a running story summary, and embedding-based retrieval that
pulls old-but-relevant facts back into context, with similarity scores visible in Insights
(
backend/app/memorybank.py). - Import/export — AI Dungeon-compatible formats for scripts and scenarios; JSON for everything.
- Optional accounts for hosted deployments — by default the app is single-user with zero
auth friction; set
AIDND_MULTI_USER=1and visitors play instantly as guests (signed session cookie), can register (email + password) at any point to keep their data, and each user gets isolated data plus their own encrypted-at-rest API key. A server-funded shared demo key with a daily turn cap lets people try it without bringing a key (backend/app/auth.py).
Quick start
Docker (any OS)
docker compose up --build
Open http://localhost:8000. Your data persists in a named volume across restarts.
Windows
cd backend; python -m venv .venv; .\.venv\Scripts\pip.exe install -r requirements.txt; cd ..
cd frontend; npm install; cd ..
.\start.ps1
Open http://localhost:5173 (dev servers; API docs at http://localhost:8000/docs).
macOS / Linux
./start.sh # creates the venv and installs dependencies on first run
Open http://localhost:5173.
Connect a model
Open Settings in the app and point it at any OpenAI-compatible endpoint:
| Provider | Endpoint URL | Notes |
|---|---|---|
| Ollama (local) | http://localhost:11434/v1 |
free, private; also serves embedding models for the Memory Bank (e.g. nomic-embed-text) |
| LM Studio (local) | http://localhost:1234/v1 |
free, private |
| OpenRouter | https://openrouter.ai/api/v1 |
:free models cost nothing (no embeddings on the free tier) |
| OpenAI / Groq / vLLM / … | provider's /v1 URL |
anything speaking /v1/chat/completions |
Model name, API key, generation parameters, and (optionally) summary/embedding models for the Memory Bank are all configured there too — no config files, no rebuild.
How a turn works
player input
→ onInput script modifier
→ assemble context: [AI instructions] + [plot essentials] + [story summary]
+ [retrieved memories] + [triggered story cards]
+ [story history, token-budgeted] + [author's note] + [player action]
→ onModelContext script modifier
→ snapshot context (Insights)
→ provider adapter → AI (streamed)
→ onOutput script modifier
→ store & render
Architecture
frontend/ React + Vite SPA ──HTTP/SSE──► backend/ FastAPI
├─ routers/ auth, scenarios, adventures, story cards, scripts, settings, debug
├─ models.py SQLAlchemy: User, Scenario, Adventure, Action, StoryCard, Script, Settings, Memory
├─ auth.py guest/registered users, sessions, shared demo key
├─ security.py password hashing, cookie signing, API-key encryption
├─ context/ prompt assembly under a token budget
├─ scripting/ quickjs sandbox + AI Dungeon API surface
├─ memorybank.py auto-summarization + embedding retrieval
├─ providers/ OpenAI-compatible adapter, streaming
└─ data.db SQLite (path overridable via AIDND_DB_PATH)
In production the backend serves the built SPA from one port (see Dockerfile); in
development Vite proxies /api to FastAPI.
Deploy (Render)
The repo ships a render.yaml blueprint: one Docker web service that
serves the SPA and API same-origin, backed by external Neon Postgres
(the free tier has no persistent disk, so the database lives off-box).
- Create a Neon project and copy its pooled connection string.
- In Render: New → Blueprint, point it at this repo. Render reads
render.yaml. - Fill the secrets it prompts for (
sync: falsevars):AIDND_DATABASE_URL(the Neon string) and, to offer a no-signup demo,AIDND_DEMO_API_KEY/AIDND_DEMO_MODELS.AIDND_SECRET_KEYis generated automatically and kept stable across deploys. - Deploy. Pushes to
mainauto-deploy thereafter. Health check:/api/health.
On the free tier the service sleeps after ~15 min idle; the first request then takes ~30–60s to wake.
Repo notes
plan/— the phased implementation plan this was built from, kept as a build log (phases 1–6 complete; 7–10 cover the public release).backend/.env.example— the few environment variables the backend reads.CODE_REVIEW_FINDINGS.md— notes from a self-review pass.