KansaiGaijin bc7ac32de3 Add campaign layer, GPU model caching, file browser cleanup
- Campaigns: new table, CRUD API, React context + provider
- Sessions: scoped to campaigns, paths under campaigns/{id}/
- File browser: scoped per campaign, removed copy/paste/autoPlay
- Sidebar: campaign selector dropdown at top
- Transcribe: GPU model cached/released via job counter
- Jobs: status text updates dynamically in real-time
- Auto-redirect: blocked when summarize job is active
2026-07-10 21:41:29 +12:00
2026-07-06 23:55:03 +12:00

Nat20 Notes

Turn a recorded tabletop RPG session (audio or video) into two documents: a full GM/DM session log, and a spoiler-free player recap — using local transcription (WhisperX) and either a local LLM (Ollama) or any OpenAI-compatible hosted API for summarization.

Requirements

  • Docker + Docker Compose
  • An NVIDIA GPU with drivers + NVIDIA Container Toolkit installed on the host (for transcription; CPU-only works but is much slower)
  • A free HuggingFace token — needed for speaker diarization. You'll also need to accept the terms on the gated pyannote model page it links you to on first run.
  • Either: Ollama running somewhere reachable from this app (local or LAN), or an API key for a hosted LLM (OpenAI, or any OpenAI-compatible provider)

Quick start

git clone <this-repo>
cd nat20-notes
# Review docker-compose.yml — see Configuration below for env vars
docker compose up -d --build

Then open http://<your-server>:8020 (yes, that port reads "a d20") and follow the setup wizard:

  1. Choose a Whisper model size based on your GPU's available VRAM (guidance shown in-app)
  2. Choose local Ollama or a hosted API for summarization, and paste your HF token
  3. Optionally paste campaign/world context (NPC names, places) so summaries recognize them correctly

A reference compose file using named volumes only (no host paths) is at docker-compose.example.yml.

Using it

  1. Upload a recording (audio or video — video is auto-converted, audio-only files skip that step and are much faster). Files over 90 MB are auto-chunked with 3-way concurrent uploads.
  2. Transcribe — runs in the background. On completion you're automatically taken to the speaker-naming screen.
  3. Name your speakers — a waveform-style "session reel" shows each detected speaker's segments; click any point to jump the audio there and hear who's talking, then type in their name. Use the checkboxes to merge speakers (e.g. when diarization over-splits one person into SPEAKER_00, SPEAKER_05, etc.).
  4. Generate notes — click "Done naming" and confirm; notes generation starts automatically and you're taken to the job progress screen. When finished, navigate to the notes viewer.
  5. Review & tweak — regenerate notes, rename speakers, or delete jobs/sessions from the session detail page.

Data layout

The app stores everything under /data (inside the container), which by default is a bind mount to a host path of your choice:

Directory / File Contents
audio/ Uploaded recordings and extracted audio
transcriptions/ Per-session transcript JSON files
notes/ Generated notes (GM log + player recap)
app.db SQLite database (sessions, speakers, settings, jobs)

Configuration

Prefilling the setup wizard

Set NAT20_* environment variables under the backend service in docker-compose.yml — the wizard will pick them up as defaults:

environment:
  NAT20_HF_TOKEN: "hf_..."
  NAT20_WHISPER_MODEL: medium
  NAT20_OLLAMA_HOST: http://localhost:11434
  NAT20_WORLD_CONTEXT_PATH: /data/campaign-context.txt

See the environment: block in docker-compose.yml for the full list.

Campaign context

You can paste context directly in the Settings page, or point to a file inside the container using the world_context_path setting (or the NAT20_WORLD_CONTEXT_PATH env var). The file path version is useful for large campaign bibles that you update independently.

Notes on hardware

Transcription is GPU-bound and by far the slowest step for long sessions. Summarization is comparatively light — a 7-8B parameter local model is sufficient for most groups; only step up in size if you have the VRAM headroom after Whisper's footprint (they don't run at the same time, so you only need enough VRAM for whichever is currently running, plus normal system overhead from other GPU-using services).

Architecture

  • backend/ — FastAPI, SQLite (no external DB needed), WhisperX as a library (no nested Docker), in-process ThreadPoolExecutor background jobs (no Redis/Celery)
  • frontend/ — React 18 + Vite + Tailwind, served via nginx (listens on port 8020) which proxies /api to the backend

Both run as standard Docker Compose services — no special orchestration needed beyond GPU passthrough for the backend.

Description
AI audio transcription and auto-summarisation program
Readme MIT 302 KiB
Languages
TypeScript 51.4%
Python 46.3%
Makefile 0.7%
Dockerfile 0.5%
CSS 0.4%
Other 0.7%