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
# Clone and start
git clone <repo-url> && 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)
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:
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:
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:
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:
# 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 <token> header.
Auth
POST /auth/register— Register (ifallow_registrationpolicy is true)POST /auth/login— Login, returns access + refresh tokensPOST /auth/refresh— Refresh access tokenPOST /auth/logout— Revoke refresh token
Channels & Messages
GET/POST /channels— List/create conversationsGET/PUT/DELETE /channels/:id— Channel CRUDGET/POST /channels/:id/messages— Message list/createPOST /chat/completions— Stream AI completions (SSE)POST /channels/:id/messages/:msgId/edit— Edit and forkPOST /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 configsGET/PUT/DELETE /api-configs/:id— Provider CRUDPOST /api-configs/:id/models/fetch— Refresh models from provider APIGET /api-configs/:id/models— List models for a personal provider
Teams
GET /teams— Teams the user belongs toGET/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 managementGET/PUT /admin/settings/:key— Global settings and policiesGET/POST /admin/configs— Global provider configsGET/POST /admin/models/fetch— Model catalog syncGET /admin/audit— Audit log
Development
# 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.