Khalid A 3cf3694ee7 Add message intent classification (edit/info/help) before routing
Introduces a two-LLM-call pipeline: the first call classifies the user's
message intent as "edit", "info", or "help". Edit messages proceed through
the existing routing → edit → propose flow. Info messages get a generated
response about site content from the manifest. Help messages get a
templated capabilities overview. This handles open-ended questions like
"What can I do?" or "What does my site have on it?" which previously
had no path through the system.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 20:17:22 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00
2026-04-17 16:08:31 -05:00

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 (Telnyx)  │  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

Local Development

# 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

See .env.example for all options.

Docker

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/              # Telnyx 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/
Description
No description provided
Readme 626 KiB
Languages
TypeScript 57.5%
Astro 34.8%
JavaScript 2.8%
CSS 2.7%
Dockerfile 1.2%
Other 1%