160 lines
7.4 KiB
Markdown
160 lines
7.4 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 │
|
|
└───────┬─────────┴────────┬─────────┴─────┬──────┘
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌─────────────────────────────────────────────────┐
|
|
│ 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 volume
|
|
│ 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 generated when creating the Vonage application
|
|
- `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://smsapi.kadil.dev/webhooks/inbound` (POST).
|
|
3. Set the status webhook URL to `https://smsapi.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.
|
|
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
|
|
```
|
|
|
|
## 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
|
|
├── docker-compose.yml # Full stack (web + orchestrator)
|
|
├── Dockerfile # SSR site image
|
|
└── server/Dockerfile # Orchestrator image
|
|
```
|
|
|
|
## 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
|
|
|
|
- **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/`
|