165 lines
8.5 KiB
Markdown
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/`
|