# Chat Switchboard A self-hosted AI chat application for enterprise and government environments. Unified interface for multiple AI providers with admin controls, team management, and security features. ## Quick Start ```bash # Clone and start git clone && cd chat-switchboard docker compose up -d # Access at http://localhost:3000 # Default admin: admin / admin ``` ## Features - **Multi-Provider**: Anthropic, OpenAI, OpenRouter, Venice AI — with BYOK support - **Team Management**: Roles (admin/user) for vertical permissions, Teams for horizontal visibility - **Personas**: Preset configurations (model + system prompt + parameters) at global, team, or personal scope - **Model Catalog**: Three-state visibility (enabled/disabled/team-only) with admin controls - **Message Trees**: Edit and regenerate with full conversation forking — navigate sibling branches - **Notes**: Markdown notes with full-text search, folders, and tags - **Audit Log**: All admin operations logged with actor, action, and resource details - **Security**: JWT + refresh tokens, optional mTLS, optional OIDC/Keycloak, environment classification banners - **Airgapped**: Local vendor files (marked.js, DOMPurify), no CDN required - **Mobile**: Responsive design, PWA manifest ## Architecture ``` ┌─────────────┐ ┌──────────────────────────────┐ │ Browser │────▶│ nginx (port 80) │ │ (vanilla JS)│◀────│ ├─ /api/* → Go backend:8080 │ └─────────────┘ │ ├─ /ws → WebSocket proxy │ │ └─ /* → static files │ └──────────────────────────────┘ │ ┌─────────▼─────────┐ │ Go Backend │ │ ├─ handlers/ │ │ ├─ store/postgres/ │ │ ├─ providers/ │ │ └─ capabilities/ │ └─────────┬─────────┘ │ ┌─────────▼─────────┐ │ PostgreSQL 16 │ └───────────────────┘ ``` **Go backend** (vanilla, no framework beyond Gin) with a store layer abstracting all database access. Providers package handles LLM API calls. Capabilities package resolves model features from catalog data, known model tables, and heuristic inference. **Frontend** is vanilla JavaScript — no build step, no bundler. Five files: `api.js` (HTTP client), `app.js` (state + logic), `ui.js` (DOM rendering), `events.js` (event bus + WebSocket), `debug.js` (admin debug panel). ## Configuration All configuration via environment variables. See `server/.env.example` for the full list. | Variable | Default | Description | |----------|---------|-------------| | `PORT` | `8080` | Backend listen port | | `BASE_PATH` | ` ` | URL prefix (e.g. `/chat`) | | `DB_HOST` | `localhost` | PostgreSQL host | | `DB_NAME` | `chat_switchboard` | Database name | | `JWT_SECRET` | (required) | Token signing key | | `SWITCHBOARD_ADMIN_USERNAME` | ` ` | Bootstrap admin username | | `SWITCHBOARD_ADMIN_PASSWORD` | ` ` | Bootstrap admin password | ## Deployment Three Docker images support different scenarios: | Image | Dockerfile | Use Case | |-------|-----------|----------| | **Unified** | `Dockerfile` | Dev, docker-compose, single-node | | **Backend** | `server/Dockerfile` | K8s — scale API pods independently | | **Frontend** | `Dockerfile.frontend` | K8s — scale FE pods independently | ### Docker Compose (development — unified image) ```bash docker compose up -d # start all services docker compose up -d --build # rebuild after code changes docker compose --profile dev up # include Adminer DB UI ``` ### Kubernetes (split images) Build and push both images: ```bash docker build -f server/Dockerfile -t your-registry/switchboard-api:0.9.1 server/ docker build -f Dockerfile.frontend -t your-registry/switchboard-fe:0.9.1 . ``` **Backend deployment:** ```yaml containers: - name: api image: your-registry/switchboard-api:0.9.1 ports: - containerPort: 8080 env: - name: DB_HOST value: "postgres-service" - name: BASE_PATH value: "/chat" # must match ingress path - name: JWT_SECRET valueFrom: secretKeyRef: name: switchboard-secrets key: jwt-secret ``` **Frontend deployment:** ```yaml containers: - name: frontend image: your-registry/switchboard-fe:0.9.1 ports: - containerPort: 80 env: - name: BASE_PATH value: "/chat" # injected into index.html at startup volumeMounts: - name: branding mountPath: /branding readOnly: true # optional: custom logo, colors ``` **Ingress** routes `/api/*` and `/ws` to the backend Service, everything else to the frontend Service. The frontend entrypoint generates the nginx config dynamically based on `BASE_PATH`. ### Path-Based Routing Set `BASE_PATH=/chat` to serve the application under a subpath. The frontend reads `window.__BASE__` injected at container startup, and all API calls are prefixed automatically. ### Airgapped / Disconnected The Docker build bakes in `marked.js` and `DOMPurify` from npm during the vendor stage. No CDN calls at runtime. The `src/vendor/` directory contains local copies as fallback for development without Docker. ## Database PostgreSQL 16+ required. The `pgcrypto` extension is used for `gen_random_uuid()`. ### Schema Management The Go backend auto-migrates on startup using files in `server/database/migrations/`. For manual operations: ```bash # Bootstrap database (superuser, creates role + DB) scripts/db-bootstrap.sh # Manual migration (usually not needed) scripts/db-migrate.sh # Validate schema scripts/db-validate.sh ``` ### Key Tables (v0.9) | Table | Purpose | |-------|---------| | `users` | Accounts with role, avatar, settings | | `provider_configs` | API provider configurations (scope: global/team/personal) | | `model_catalog` | Synced model list with capabilities and visibility | | `personas` | Model presets (scope: global/team/personal) | | `channels` | Conversations with type (direct/group/channel) | | `messages` | Message tree with parent_id for forking | | `teams` / `team_members` | Organizational units | | `notes` | Markdown notes with full-text search | | `audit_log` | Admin action audit trail | | `user_model_settings` | Per-user model visibility and sort preferences | ## API All endpoints under `/api/v1/`. Authentication via `Authorization: Bearer ` header. ### Auth - `POST /auth/register` — Register (if `allow_registration` policy is true) - `POST /auth/login` — Login, returns access + refresh tokens - `POST /auth/refresh` — Refresh access token - `POST /auth/logout` — Revoke refresh token ### Channels & Messages - `GET/POST /channels` — List/create conversations - `GET/PUT/DELETE /channels/:id` — Channel CRUD - `GET/POST /channels/:id/messages` — Message list/create - `POST /chat/completions` — Stream AI completions (SSE) - `POST /channels/:id/messages/:msgId/edit` — Edit and fork - `POST /channels/:id/messages/:msgId/regenerate` — Regenerate response ### Models - `GET /models/enabled` — Models available to the user (global + team + personal) - `GET/PUT /models/preferences` — User model visibility settings ### Personal Providers (BYOK) - `GET/POST /api-configs` — User's personal provider configs - `GET/PUT/DELETE /api-configs/:id` — Provider CRUD - `POST /api-configs/:id/models/fetch` — Refresh models from provider API - `GET /api-configs/:id/models` — List models for a personal provider ### Teams - `GET /teams` — Teams the user belongs to - `GET/POST /teams/:id/providers` — Team provider management (team admins) - `GET/POST /teams/:id/presets` — Team preset management (team admins) ### Admin - `GET/POST /admin/users` — User management - `GET/PUT /admin/settings/:key` — Global settings and policies - `GET/POST /admin/configs` — Global provider configs - `GET/POST /admin/models/fetch` — Model catalog sync - `GET /admin/audit` — Audit log ## Development ```bash # Backend (requires Go 1.22+) cd server cp .env.example .env # edit with your DB credentials go run . # Frontend (just serve static files) # Use any HTTP server pointed at src/ python3 -m http.server 3000 --directory src ``` ## License Proprietary. All rights reserved.