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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KFsGHju9szibJJa2YJcdbg
This commit is contained in:
co-authored by
Claude Fable 5
parent
466d7bc0e4
commit
22630c8411
@@ -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
|
||||||
+35
@@ -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"]
|
||||||
@@ -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.
|
||||||
@@ -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
|
An AI Dungeon-style interactive storytelling app you can run entirely on your own machine —
|
||||||
OpenAI-compatible AI endpoint (Ollama, LM Studio, OpenAI, OpenRouter, …).
|
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
|
```powershell
|
||||||
|
cd backend; python -m venv .venv; .\.venv\Scripts\pip.exe install -r requirements.txt; cd ..
|
||||||
|
cd frontend; npm install; cd ..
|
||||||
.\start.ps1
|
.\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
|
```sh
|
||||||
# Terminal 1 — backend
|
./start.sh # creates the venv and installs dependencies on first run
|
||||||
cd backend
|
|
||||||
.\.venv\Scripts\uvicorn.exe app.main:app --port 8000 --reload
|
|
||||||
|
|
||||||
# Terminal 2 — frontend
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## First-time setup
|
Open http://localhost:5173.
|
||||||
|
|
||||||
```powershell
|
## Connect a model
|
||||||
cd backend
|
|
||||||
python -m venv .venv
|
|
||||||
.\.venv\Scripts\pip.exe install -r requirements.txt
|
|
||||||
|
|
||||||
cd ..\frontend
|
Open **Settings** in the app and point it at any OpenAI-compatible endpoint:
|
||||||
npm install
|
|
||||||
|
| 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:
|
```
|
||||||
|
frontend/ React + Vite SPA ──HTTP/SSE──► backend/ FastAPI
|
||||||
```powershell
|
├─ routers/ scenarios, adventures, story cards, scripts, settings, debug
|
||||||
cd frontend
|
├─ models.py SQLAlchemy: Scenario, Adventure, Action, StoryCard, Script, Settings, Memory
|
||||||
npm run build
|
├─ context/ prompt assembly under a token budget
|
||||||
# then run the backend and open http://localhost:8000
|
├─ 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/`
|
## Repo notes
|
||||||
- `backend/data.db` — SQLite database (created on first run)
|
|
||||||
- `frontend/src/` — React SPA: `pages/`, `api.js`
|
- `plan/` — the phased implementation plan this was built from, kept as a build log
|
||||||
- `plan/` — phased implementation plan
|
(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)
|
||||||
|
|||||||
@@ -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.
|
||||||
+10
-1
@@ -1,9 +1,18 @@
|
|||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from sqlalchemy import create_engine, event
|
from sqlalchemy import create_engine, event
|
||||||
from sqlalchemy.orm import DeclarativeBase, sessionmaker
|
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(
|
engine = create_engine(
|
||||||
f"sqlite:///{DB_PATH}",
|
f"sqlite:///{DB_PATH}",
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
services:
|
||||||
|
ai-dnd:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
- ai-dnd-data:/data
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
ai-dnd-data:
|
||||||
@@ -10,36 +10,42 @@ immediately usable on a resume — before any hosted-deployment work.
|
|||||||
| License | **MIT** |
|
| License | **MIT** |
|
||||||
| README media (screenshots/GIF) | **Skip for now** — text-only README; visuals in a later pass |
|
| 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
|
**Repo name (decided): `AI-DnD`.** Ask before publishing: whether the existing commit
|
||||||
existing local commit history/message is fine to publish as-is.
|
history/messages are fine to publish as-is.
|
||||||
|
|
||||||
## Repo hygiene
|
## Repo hygiene
|
||||||
|
|
||||||
- [ ] Add `LICENSE` (MIT, current year, Parth Thakkar).
|
- [x] Add `LICENSE` (MIT, current year, Parth Thakkar).
|
||||||
- [ ] Verify no secrets or user data are tracked (`openrouter_key.env`, `data.db` — already
|
- [x] Verify no secrets or user data are tracked (`openrouter_key.env`, `data.db` — already
|
||||||
gitignored and never committed; re-verify before push).
|
gitignored and never committed; re-verify before push).
|
||||||
- [ ] Add `backend/.env.example` documenting every env var the app reads (grows in Phase 9).
|
- [x] 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
|
(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.
|
for a portfolio) — just give them a one-line mention in the README.
|
||||||
|
|
||||||
## README rewrite (portfolio-grade, text-only)
|
## 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).
|
memory bank with embeddings, full prompt transparency/Insights, provider-agnostic).
|
||||||
- [ ] Feature list with pointers into the code (scripting engine, context builder, memory bank).
|
- [x] Feature list with pointers into the code (scripting engine, context builder, memory bank).
|
||||||
- [ ] Architecture diagram (reuse/refresh the one in `plan/00-OVERVIEW.md`).
|
- [x] Architecture diagram (reuse/refresh the one in `plan/00-OVERVIEW.md`).
|
||||||
- [ ] Setup instructions for **Windows (start.ps1), macOS/Linux (manual), and Docker**.
|
- [x] 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] "Bring your own model" section: Ollama / LM Studio / OpenRouter free models — emphasize it
|
||||||
runs fully free.
|
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)
|
## 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`).
|
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.
|
(3 stages: node build → pip wheel build with gcc for quickjs → slim runtime.)
|
||||||
- [ ] `start.sh` for macOS/Linux dev parity with `start.ps1` (optional, nice-to-have).
|
- [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`.
|
- [ ] 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
|
## Publish
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user