From 22630c84117454c862574b96b7d0e1b33899c16f Mon Sep 17 00:00:00 2001 From: parththakkar106 Date: Mon, 6 Jul 2026 16:51:48 +0530 Subject: [PATCH] Phase 7: MIT license, portfolio README, Docker, cross-platform start - LICENSE: MIT - README: rewritten as portfolio-grade docs (features with code pointers, architecture, Docker/Windows/macOS quick starts, BYO-model table) - Dockerfile (3-stage: SPA build, pip wheels, slim runtime) + compose with a /data volume; .dockerignore keeps secrets and local data out - database.py: AIDND_DB_PATH env override so deployments can relocate the SQLite file; documented in backend/.env.example - start.sh: macOS/Linux dev script with first-run setup Verified locally: production SPA build served by the backend (deep links OK), DB created at the override path. Docker image itself untested here (Docker not installed); flagged in plan/07. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KFsGHju9szibJJa2YJcdbg --- .dockerignore | 19 +++++ Dockerfile | 35 +++++++++ LICENSE | 21 ++++++ README.md | 133 ++++++++++++++++++++++++++--------- backend/.env.example | 17 +++++ backend/app/database.py | 11 ++- docker-compose.yml | 11 +++ plan/07-phase-public-repo.md | 36 ++++++---- start.sh | 23 ++++++ 9 files changed, 256 insertions(+), 50 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 backend/.env.example create mode 100644 docker-compose.yml create mode 100644 start.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3b11fe3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +.git +.claude +plan +*.md +LICENSE +start.ps1 +start.sh + +# Secrets & local data — never in the image +*.env +backend/openrouter_key.env +*.db + +# Build/runtime artifacts rebuilt inside the image +backend/.venv +**/__pycache__ +**/*.pyc +frontend/node_modules +frontend/dist diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d020e34 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +# Stage 1 — build the React SPA +FROM node:22-alpine AS frontend-build +WORKDIR /build +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci +COPY frontend/ ./ +RUN npm run build + +# Stage 2 — build Python wheels (quickjs compiles from source if no wheel +# matches, so keep the toolchain out of the final image) +FROM python:3.12-slim AS python-build +RUN apt-get update && apt-get install -y --no-install-recommends gcc make \ + && rm -rf /var/lib/apt/lists/* +COPY backend/requirements.txt /tmp/requirements.txt +RUN pip wheel --no-cache-dir -r /tmp/requirements.txt -w /wheels + +# Stage 3 — runtime +FROM python:3.12-slim +WORKDIR /app + +COPY --from=python-build /wheels /wheels +RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels + +# Layout mirrors the repo: main.py finds the SPA at ../../frontend/dist +# relative to backend/app/main.py. +COPY backend/app /app/backend/app +COPY --from=frontend-build /build/dist /app/frontend/dist + +# Database lives on a volume; parent dir is created by the app if missing. +ENV AIDND_DB_PATH=/data/data.db +VOLUME /data + +EXPOSE 8000 +WORKDIR /app/backend +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9464f62 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Parth Thakkar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 9fb7b6a..8464d64 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,119 @@ -# AI D&D — local AI Dungeon-style storytelling app +# AI D&D -Python (FastAPI + SQLite) backend, React (Vite) frontend, connectable to any -OpenAI-compatible AI endpoint (Ollama, LM Studio, OpenAI, OpenRouter, …). +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**. -Plan: see [`plan/00-OVERVIEW.md`](plan/00-OVERVIEW.md). Currently completed: **Phase 4** (AI Dungeon-compatible scripting). +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. -## Run +> 📸 *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` / `onOutput` + modifiers with shared `state` and a `worldEntries` API, 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. + +## Quick start + +### Docker (any OS) + +```sh +docker compose up --build +``` + +Open http://localhost:8000. Your data persists in a named volume across restarts. + +### Windows ```powershell +cd backend; python -m venv .venv; .\.venv\Scripts\pip.exe install -r requirements.txt; cd .. +cd frontend; npm install; cd .. .\start.ps1 ``` -Then open http://localhost:5173. Backend API docs at http://localhost:8000/docs. +Open http://localhost:5173 (dev servers; API docs at http://localhost:8000/docs). -Or manually, in two terminals: +### macOS / Linux -```powershell -# Terminal 1 — backend -cd backend -.\.venv\Scripts\uvicorn.exe app.main:app --port 8000 --reload - -# Terminal 2 — frontend -cd frontend -npm run dev +```sh +./start.sh # creates the venv and installs dependencies on first run ``` -## First-time setup +Open http://localhost:5173. -```powershell -cd backend -python -m venv .venv -.\.venv\Scripts\pip.exe install -r requirements.txt +## Connect a model -cd ..\frontend -npm install +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 ``` -## Production-ish serving (single port) +## Architecture -Build the frontend, then the backend serves it statically: - -```powershell -cd frontend -npm run build -# then run the backend and open http://localhost:8000 +``` +frontend/ React + Vite SPA ──HTTP/SSE──► backend/ FastAPI + ├─ routers/ scenarios, adventures, story cards, scripts, settings, debug + ├─ models.py SQLAlchemy: Scenario, Adventure, Action, StoryCard, Script, Settings, Memory + ├─ 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) ``` -## Layout +In production the backend serves the built SPA from one port (see `Dockerfile`); in +development Vite proxies `/api` to FastAPI. -- `backend/app/` — FastAPI app: `models.py` (SQLAlchemy), `schemas.py` (Pydantic), `routers/` -- `backend/data.db` — SQLite database (created on first run) -- `frontend/src/` — React SPA: `pages/`, `api.js` -- `plan/` — phased implementation plan +## 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. + +## License + +[MIT](LICENSE) diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..6edfe2c --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,17 @@ +# Environment variables read by the backend. +# +# NOTE: the app reads real environment variables — it does NOT auto-load this +# file. Set them in your shell, in docker-compose.yml, or in your host's +# dashboard. This file is documentation (and a template for deploy configs). + +# Absolute path for the SQLite database file. Parent directory is created if +# missing. Default when unset: backend/data.db +# Docker compose sets this to /data/data.db (a named volume). +AIDND_DB_PATH= + +# --- Coming in later phases (documented here as they land) --- +# Phase 8/9 will add: SECRET_KEY, MULTI_USER, DEMO_API_KEY, DEMO_ENDPOINT_URL, +# DEMO_MODEL_WHITELIST, DEMO_TURNS_PER_DAY, CORS_ORIGINS. +# +# The AI endpoint/API key/model are NOT env vars — they are configured at +# runtime in the app's Settings page and stored in the database. diff --git a/backend/app/database.py b/backend/app/database.py index fbb3d8b..01cb33a 100644 --- a/backend/app/database.py +++ b/backend/app/database.py @@ -1,9 +1,18 @@ +import os from pathlib import Path from sqlalchemy import create_engine, event from sqlalchemy.orm import DeclarativeBase, sessionmaker -DB_PATH = Path(__file__).resolve().parent.parent / "data.db" +# AIDND_DB_PATH lets deployments (Docker volume, hosted disk) relocate the +# database; default stays backend/data.db for local runs. +_env_db_path = os.environ.get("AIDND_DB_PATH") +DB_PATH = ( + Path(_env_db_path).resolve() + if _env_db_path + else Path(__file__).resolve().parent.parent / "data.db" +) +DB_PATH.parent.mkdir(parents=True, exist_ok=True) engine = create_engine( f"sqlite:///{DB_PATH}", diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..93b65ef --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +services: + ai-dnd: + build: . + ports: + - "8000:8000" + volumes: + - ai-dnd-data:/data + restart: unless-stopped + +volumes: + ai-dnd-data: diff --git a/plan/07-phase-public-repo.md b/plan/07-phase-public-repo.md index 73c952a..e1368e0 100644 --- a/plan/07-phase-public-repo.md +++ b/plan/07-phase-public-repo.md @@ -10,36 +10,42 @@ immediately usable on a resume — before any hosted-deployment work. | License | **MIT** | | README media (screenshots/GIF) | **Skip for now** — text-only README; visuals in a later pass | -**Ask before implementing:** GitHub repo name (default suggestion: `ai-dnd`) and whether the -existing local commit history/message is fine to publish as-is. +**Repo name (decided): `AI-DnD`.** Ask before publishing: whether the existing commit +history/messages are fine to publish as-is. ## Repo hygiene -- [ ] Add `LICENSE` (MIT, current year, Parth Thakkar). -- [ ] Verify no secrets or user data are tracked (`openrouter_key.env`, `data.db` — already +- [x] Add `LICENSE` (MIT, current year, Parth Thakkar). +- [x] Verify no secrets or user data are tracked (`openrouter_key.env`, `data.db` — already gitignored and never committed; re-verify before push). -- [ ] Add `backend/.env.example` documenting every env var the app reads (grows in Phase 9). -- [ ] Decide what to do with `CODE_REVIEW_FINDINGS.md` and `plan/` — keep (shows process, good +- [x] Add `backend/.env.example` documenting every env var the app reads (grows in Phase 9). + (Currently just `AIDND_DB_PATH`, added to `database.py` for Docker/hosted volumes.) +- [x] Decide what to do with `CODE_REVIEW_FINDINGS.md` and `plan/` — keep (shows process, good for a portfolio) — just give them a one-line mention in the README. ## README rewrite (portfolio-grade, text-only) -- [ ] Pitch paragraph: what it is, what makes it interesting (AI Dungeon-compatible scripting, +- [x] Pitch paragraph: what it is, what makes it interesting (AI Dungeon-compatible scripting, memory bank with embeddings, full prompt transparency/Insights, provider-agnostic). -- [ ] Feature list with pointers into the code (scripting engine, context builder, memory bank). -- [ ] Architecture diagram (reuse/refresh the one in `plan/00-OVERVIEW.md`). -- [ ] Setup instructions for **Windows (start.ps1), macOS/Linux (manual), and Docker**. -- [ ] "Bring your own model" section: Ollama / LM Studio / OpenRouter free models — emphasize it +- [x] Feature list with pointers into the code (scripting engine, context builder, memory bank). +- [x] Architecture diagram (reuse/refresh the one in `plan/00-OVERVIEW.md`). +- [x] Setup instructions for **Windows (start.ps1), macOS/Linux (manual), and Docker**. +- [x] "Bring your own model" section: Ollama / LM Studio / OpenRouter free models — emphasize it runs fully free. -- [ ] Placeholder section for screenshots/GIF (added in a later pass). +- [x] Placeholder section for screenshots/GIF (added in a later pass). ## Docker (one-command run for non-Windows users) -- [ ] `Dockerfile`: multi-stage — build frontend (`npm run build`), then Python image serving +- [x] `Dockerfile`: multi-stage — build frontend (`npm run build`), then Python image serving FastAPI with the built SPA mounted (SPA fallback already exists in `app/main.py`). -- [ ] `docker-compose.yml`: single service, volume for `data.db`, port mapping. -- [ ] `start.sh` for macOS/Linux dev parity with `start.ps1` (optional, nice-to-have). + (3 stages: node build → pip wheel build with gcc for quickjs → slim runtime.) +- [x] `docker-compose.yml`: single service, named volume mounted at `/data` + (`AIDND_DB_PATH=/data/data.db`), port mapping. +- [x] `start.sh` for macOS/Linux dev parity with `start.ps1` (optional, nice-to-have). - [ ] Test: `docker compose up` from a clean clone → app works at `http://localhost:8000`. + **Blocked locally: Docker is not installed on the dev machine.** Verified without Docker: + production frontend build + backend serving the SPA (deep links OK) + DB-path override + all work. Test the image on any Docker machine (or let Render build it in Phase 10). ## Publish diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..a26e7e7 --- /dev/null +++ b/start.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# macOS/Linux equivalent of start.ps1: backend (FastAPI, :8000) and frontend +# dev server (Vite, :5173). Ctrl-C stops both. +set -euo pipefail +cd "$(dirname "$0")" + +if [ ! -d backend/.venv ]; then + echo "First-time setup: creating backend/.venv and installing dependencies..." + python3 -m venv backend/.venv + backend/.venv/bin/pip install -r backend/requirements.txt +fi +if [ ! -d frontend/node_modules ]; then + echo "First-time setup: npm install..." + (cd frontend && npm install) +fi + +(cd backend && .venv/bin/uvicorn app.main:app --port 8000 --reload) & +BACKEND_PID=$! +trap 'kill "$BACKEND_PID" 2>/dev/null' EXIT + +echo "Backend: http://localhost:8000 (API docs: http://localhost:8000/docs)" +echo "Frontend: http://localhost:5173" +cd frontend && npm run dev