diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..511a292 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,29 @@ +name: Build and Push +on: + push: + branches: [main] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - name: Login to Gitea Container Registry + run: echo "${{ secrets.GITEA_TOKEN }}" | docker login gitea.kansaigaijin.com -u ${{ gitea.repository_owner }} --password-stdin + + - name: Build and push backend + uses: docker/build-push-action@v5 + with: + context: ./backend + target: runtime + push: true + tags: gitea.kansaigaijin.com/${{ gitea.repository }}/backend:latest + + - name: Build and push frontend + uses: docker/build-push-action@v5 + with: + context: ./frontend + push: true + tags: gitea.kansaigaijin.com/${{ gitea.repository }}/frontend:latest diff --git a/README.md b/README.md index 6577e30..7fc38f8 100644 --- a/README.md +++ b/README.md @@ -40,39 +40,62 @@ A reference compose file using named volumes only (no host paths) is at ## Data layout -The app stores everything under `/data` (inside the container), which by default -is a [bind mount](./docker-compose.yml) to a host path of your choice: +The app stores everything under `/data` (inside the container): | 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) | +| `campaigns/{id}/audio/` | Uploaded recordings and extracted audio, organised per campaign | +| `campaigns/{id}/transcriptions/` | Per-session transcript JSON files | +| `campaigns/{id}/notes/` | Generated notes (GM log + player recap) | +| `app.db` | SQLite database (sessions, speakers, campaigns, settings, jobs) | ## Configuration -### Prefilling the setup wizard +### Environment variables (`NAT20_*`) -Set `NAT20_*` environment variables under the `backend` service in -`docker-compose.yml` — the wizard will pick them up as defaults: +Set these under the `backend` service in `docker-compose.yml` to prefill +the setup wizard and override defaults. All are optional — the wizard +and Settings page can set them at runtime. -```yaml -environment: - NAT20_HF_TOKEN: "hf_..." - NAT20_WHISPER_MODEL: medium - NAT20_OLLAMA_HOST: http://localhost:11434 - NAT20_WORLD_CONTEXT_PATH: /data/campaign-context.txt -``` +#### Transcription -See the `environment:` block in `docker-compose.yml` for the full list. +| Variable | What it does | Values | Default | Notes | +|---|---|---|---|---| +| `NAT20_WHISPER_MODEL` | Whisper model size | `tiny` `base` `small` `medium` `large-v3` | `medium` | Larger = more accurate but uses more VRAM. `medium` fits most 6-8 GB GPUs; `large-v3` needs ~10 GB+ | +| `NAT20_WHISPER_COMPUTE_TYPE` | Compute precision | `int8` `float16` `float32` | `int8` | `int8` = fastest / least VRAM. `float16` = more accurate, more VRAM. `float32` = full precision, slowest | +| `NAT20_HF_TOKEN` | HuggingFace token for speaker diarization | `hf_...` | (none) | Required for speaker attribution. Must accept pyannote gated-model terms with the same account first | + +#### LLM backend + +| Variable | What it does | Values | Default | Notes | +|---|---|---|---|---| +| `NAT20_OLLAMA_HOST` | Ollama server URL | URL | `http://localhost:11434` | Set to your Ollama host | +| `NAT20_OLLAMA_MODEL` | Ollama model name | any model on your server | `qwen2.5:7b` | | +| `NAT20_API_BASE_URL` | OpenAI-compatible API base | URL | `https://api.openai.com/v1` | Uncomment and set to switch from Ollama | +| `NAT20_API_KEY` | API key | string | (none) | | +| `NAT20_API_MODEL` | API model name | string | `gpt-4o-mini` | | + +#### Summarization + +| Variable | What it does | Values | Default | Notes | +|---|---|---|---|---| +| `NAT20_CHUNK_WORD_TARGET` | Target words per summarisation chunk | number | `2500` | Long transcripts are split into chunks, each summarised separately, then combined. **Lower** = more LLM calls but finer granularity (good for models with small context windows). **Higher** = more context per chunk but may exceed the model's window. `2500` is safe for most models (8K–128K context) | +| `NAT20_WORLD_CONTEXT` | Campaign context string | text | (none) | Injected into every summarisation prompt so the LLM recognises NPCs, places, and lore correctly | +| `NAT20_WORLD_CONTEXT_PATH` | Path to campaign context file inside container | container path | (none) | Alternative to `NAT20_WORLD_CONTEXT` for large campaign bibles you update independently | +| `NAT20_PLAYER_RECAP_STYLE` | Player recap format | `story` `diary` `bullets` `custom` | `story` | | +| `NAT20_PLAYER_RECAP_CUSTOM_PROMPT` | Custom prompt (only when style is `custom`) | text | (none) | | + +#### Setup wizard + +| Variable | What it does | Values | Default | Notes | +|---|---|---|---|---| +| `NAT20_ONBOARDING_COMPLETED` | Skip the setup wizard | `true` or `false` | `false` | Set to `"true"` after you finish the wizard once | ### 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. +inside the container using `NAT20_WORLD_CONTEXT_PATH`. The file version is +useful for large campaign bibles that you update independently. ## Notes on hardware diff --git a/backend/app/routers/settings.py b/backend/app/routers/settings.py index fef84e0..d86ad95 100644 --- a/backend/app/routers/settings.py +++ b/backend/app/routers/settings.py @@ -25,6 +25,8 @@ _ENV_OVERRIDES = { "chunk_word_target": "NAT20_CHUNK_WORD_TARGET", "world_context": "NAT20_WORLD_CONTEXT", "world_context_path": "NAT20_WORLD_CONTEXT_PATH", + "player_recap_style": "NAT20_PLAYER_RECAP_STYLE", + "player_recap_custom_prompt": "NAT20_PLAYER_RECAP_CUSTOM_PROMPT", } _BOOL_KEYS = {"onboarding_completed"} diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 2903e77..7eab17e 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -1,26 +1,69 @@ -# Example Docker Compose — uses a named volume so data persists across rebuilds -# without needing a specific host path. Copy to docker-compose.yml and customise. +# Example Docker Compose — copy to docker-compose.yml and customise. +# Build locally: docker compose build +# Pull pre-built: docker compose pull +# Run: docker compose up -d services: - whisperx-base: - build: - context: ./backend - target: whisperx-base - image: nat20-whisperx-base:latest - restart: "no" - backend: - build: - context: ./backend - target: runtime - image: nat20-backend:latest + # ── Pre-built image (pull from registry) ────────────────── + image: gitea.kansaigaijin.com/Jamie/Nat20-Notes/backend:latest + # ── Or build locally (uncomment below) ──────────────────── + # build: + # context: ./backend + # target: runtime + restart: unless-stopped - depends_on: - - whisperx-base environment: - # Skip setup wizard on container restart (or "false" to force re-run) - NAT20_ONBOARDING_COMPLETED: "true" + # ── Setup wizard ────────────────────────────────────── + # "false" (or omit) to show the wizard on first run. + # Set to "true" after onboarding completes. + NAT20_ONBOARDING_COMPLETED: "false" + + # ── Transcription ───────────────────────────────────── + # Model size: tiny | base | small | medium | large-v3 + NAT20_WHISPER_MODEL: medium + # Compute precision: int8 (fastest/least VRAM) + # | float16 (more accurate) + # | float32 (full precision, slowest) + NAT20_WHISPER_COMPUTE_TYPE: int8 + + # Required for speaker diarization (accept HF gated-model terms first) + # NAT20_HF_TOKEN: "hf_..." + + # ── LLM backend (Ollama — local) ────────────────────── + NAT20_OLLAMA_HOST: http://localhost:11434 + NAT20_OLLAMA_MODEL: qwen2.5:7b + + # ── LLM backend (API — OpenAI-compatible) ───────────── + # Uncomment these to use a hosted API instead of Ollama + # NAT20_API_BASE_URL: https://api.openai.com/v1 + # NAT20_API_KEY: "sk-..." + # NAT20_API_MODEL: gpt-4o-mini + + # ── Summarization ───────────────────────────────────── + # Target words per chunk. Long transcripts are split into + # chunks, each summarized separately. Lower = more LLM + # calls but finer granularity. Higher = more context per + # chunk but may exceed the model's context window. + # Default 2500 works for most models (8K–128K context). + # NAT20_CHUNK_WORD_TARGET: "2500" + + # Campaign context injected into every summarization prompt. + # Inline string or path to a file inside the container. + # NAT20_WORLD_CONTEXT: "" + # NAT20_WORLD_CONTEXT_PATH: /data/campaign-context.txt + + # Player recap format: story | diary | bullets | custom + # NAT20_PLAYER_RECAP_STYLE: story + # NAT20_PLAYER_RECAP_CUSTOM_PROMPT: "" + volumes: + # ── Data persistence ────────────────────────────────── + # Option A: Named volume (auto-managed, no host path needed) - app_data:/data + # Option B: Host bind mount (replace with your path) + # - /mnt/media/dnd-sessions:/data + + # HuggingFace + Torch model caches (avoid re-downloading) - hf_cache:/root/.cache/huggingface - torch_cache:/root/.cache/torch @@ -35,7 +78,8 @@ services: - "8000:8000" frontend: - build: ./frontend + image: gitea.kansaigaijin.com/Jamie/Nat20-Notes/frontend:latest + # build: ./frontend restart: unless-stopped depends_on: - backend