Files
dynamic-sites-simple/README.md
2026-04-23 00:32:32 -05:00

165 lines
8.5 KiB
Markdown

# Dynamic Sites
An LLM-powered website editing framework. Edit your site via SMS, a web API, or a visual editor — all driven by natural language.
## Architecture
```
┌─────────────────────────────────────────────────┐
│ Channels │
│ SMS (Vonage) │ POST /api/edit │ /editor │
└───────┬─────────┴────────┬─────────┴─────┬──────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────┐
│ Single Container (entrypoint.sh) │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ Orchestrator (Express, port 3001) │ │
│ │ │ │
│ │ Webhook ──► Idempotency ──► Rate Limit │ │
│ │ │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ In-Process FIFO Queue (concurrency 1) │ │
│ │ │ propose: route ► LLM ► proposal │ │ │
│ │ │ apply: validate ► writeContentFile│ │ │
│ │ └──────────────────────────────────────┘ │ │
│ │ │ │
│ │ SQLite: idempotency, proposals, rate │ │
│ │ limits, audit log │ │
│ └──────────────────┬─────────────────────────┘ │
│ │ writes canonical JSON │
│ ▼ │
│ ┌──────────────────┐ │
│ │ content/ (JSON) │ shared filesystem │
│ │ site-context.json│ │
│ └────────┬─────────┘ │
│ │ reads with TTL cache │
│ ▼ │
│ ┌────────────────────────────────────────────┐ │
│ │ Astro SSR (port 4321) │ │
│ │ Homepage + Editor │ │
│ └────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
```
## Quick Start
### Prerequisites
- Node.js 22+
- npm
- A Vonage API account with a Messages API-enabled application
### Local Development
```bash
# Clone and install
npm install
# Start the Astro dev server (port 4321)
npm run dev
# In another terminal, start the orchestrator (port 3001)
npm run dev:server
# Visit http://localhost:4321 — the demo site renders from fixtures
# Visit http://localhost:4321/editor — log in with API_EDIT_SECRET
```
### Environment Variables
Copy `.env.example` to `.env` and set at minimum:
- `API_EDIT_SECRET` — shared secret for API auth and editor login
- `OLLAMA_API_KEY` — required for LLM-powered edits
- `VONAGE_API_KEY` — your Vonage API key (from Dashboard)
- `VONAGE_API_SECRET` — your Vonage API secret (from Dashboard)
- `VONAGE_APPLICATION_ID` — your Vonage application ID
- `VONAGE_PRIVATE_KEY_PATH` — path to the `private.key` file on disk, or set `VONAGE_PRIVATE_KEY` to the base64-encoded PEM content
- `VONAGE_API_SIGNATURE_SECRET` — webhook signature secret (from Dashboard → API Settings)
See `.env.example` for all options.
### Vonage Setup
1. Create a Vonage application in the Dashboard with Messages capability enabled.
2. Set the inbound message webhook URL to `https://dynamicsites.kadil.dev/webhooks/inbound` (POST).
3. Set the status webhook URL to `https://dynamicsites.kadil.dev/webhooks/status` (POST).
4. Under API Settings, ensure Messages API is set as the default for SMS.
5. Copy the generated `private.key` to the project root (or base64-encode it for `VONAGE_PRIVATE_KEY`).
6. Note your signature secret from Dashboard → API Settings for webhook verification.
### Docker
```bash
docker compose build
docker compose up -d
# Site: http://localhost:4321
# Orchestrator: http://localhost:3001/health
```
Both the Astro SSR site and the orchestrator run in a single container, sharing the same `content/` directory. The entrypoint script starts both processes and seeds content from the image into the volume on first deploy.
## Project Structure
```
├── content/ # Canonical JSON content (the "database")
│ ├── sections/ # One JSON file per site section
│ ├── events.json # Upcoming events
│ └── .backups/ # Pre-apply backups (auto-managed)
├── config/
│ └── sms-sites.json # SMS routing allowlist
├── site-context.json # Brand tone, style, LLM prompt context
├── shared/ # Zod schemas + canonical JSON (workspace pkg)
│ └── src/
│ ├── schemas/index.ts # All Zod schemas (the contract)
│ ├── canonical-json.ts # Sorted-key JSON serialization
│ └── repo-validation.ts# Path → schema mapping
├── server/ # Orchestrator (workspace pkg)
│ └── src/
│ ├── index.ts # Entrypoint + graceful shutdown
│ ├── app.ts # Express app factory
│ ├── db.ts # SQLite (idempotency, proposals, audit)
│ ├── logger.ts # Structured logging (pino)
│ ├── queue/ # FIFO queue + job processor
│ ├── routes/ # API edit, SMS webhook, health
│ ├── llm/ # Ollama client with retry/validation
│ ├── sms/ # Vonage parse, reply, templates
│ └── io/ # Filesystem writer (atomic, with backup)
├── src/ # Astro SSR site
│ ├── pages/
│ │ ├── index.astro # Homepage (renders from content/)
│ │ └── editor.astro # Editor (auth-gated React island)
│ ├── lib/
│ │ ├── site-bundle.ts # Content parser + validator
│ │ └── site-data.ts # Disk reader with TTL cache
│ ├── layouts/
│ │ └── BaseLayout.astro
│ └── components/
│ ├── sections/ # Astro section components
│ └── editor/ # React editor island
├── scripts/ # CLI tools
├── Dockerfile # Combined image (Astro SSR + orchestrator)
├── docker-compose.yml # Full stack (single service)
└── entrypoint.sh # Starts both processes + seeds content
```
## Edit Flow
1. **User sends a natural language message** (SMS, HTTP, or editor)
2. **Route**: LLM determines which content file to edit
3. **Propose**: LLM generates new JSON + plain-language summary
4. **Confirm**: User replies YES/NO (SMS) or clicks confirm (editor/HTTP)
5. **Apply**: Validated JSON is written to disk via atomic write
6. **Live**: Astro SSR picks up the change on next request (TTL cache)
## Key Design Decisions
- **Single container**: Both Astro SSR and the orchestrator run in one container, sharing the filesystem for content reads and writes
- **No Redis, no BullMQ**: Simple in-process FIFO queue with concurrency 1
- **No git**: Content persistence is filesystem-only
- **SQLite for everything**: Idempotency, proposals, rate limits, audit log
- **Zod is the contract**: Schemas drive validation at every boundary
- **Atomic writes**: temp file + rename prevents partial writes
- **Pre-write backups**: Last 20 versions per file under `content/.backups/`