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
- Projects: Group related conversations, KBs, and notes into workspaces with scope-aware visibility
- 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
- Extensions: Browser extension system with custom renderers, tool bridge, and self-contained styling
- Built-in: Mermaid diagrams, KaTeX math, CSV tables, diff viewer, JS sandbox, regex tester
- Server Tools: Calculator and datetime tools (auto-registered, zero config)
- Audit Log: All admin operations logged with actor, action, and resource details
- Security: JWT + refresh tokens, AES-256-GCM API key encryption, optional mTLS, optional OIDC/Keycloak, environment classification banners
- Airgapped: Local vendor files (marked.js, DOMPurify, KaTeX, mermaid.js), no CDN required
- Mobile: Responsive design, PWA manifest
- Real-time: WebSocket event bus for tool bridge, live updates
- Notifications: In-app notification bell with unread badge, per-type email delivery (SMTP), user preference controls, admin SMTP configuration
- Multi-model: @mention routing for multiple AI models per channel, fan-out completions, model attribution
Architecture
┌─────────────┐ ┌──────────────────────────────┐
│ Browser │────▶│ nginx (port 80) │
│ (vanilla JS)│◀────│ ├─ /api/* → Go backend:8080 │
└─────────────┘ │ ├─ /ws → WebSocket proxy │
│ └─ /* → static files │
└──────────────────────────────┘
│
┌─────────▼─────────┐
│ Go Backend │
│ ├─ handlers/ │
│ ├─ store/postgres/ │
│ ├─ store/sqlite/ │
│ ├─ providers/ │
│ └─ capabilities/ │
└─────────┬─────────┘
│
┌─────────▼─────────┐
│ PostgreSQL 16 or │
│ SQLite (embedded) │
└───────────────────┘
Go backend (vanilla, no framework beyond Gin) with a store layer abstracting all database access. Dual-driver architecture: store/postgres for production deployments, store/sqlite for single-user, edge, and development scenarios — selected at startup via DB_DRIVER. Providers package handles LLM API calls. Capabilities package resolves model features from catalog data, known model tables, and heuristic inference. Server tools (calculator, datetime) auto-register via init(). EventBus + WebSocket hub routes tool calls between backend and browser extensions.
Frontend is vanilla JavaScript — no build step, no bundler. 15 files organized by domain: api.js (HTTP client), app.js (state + init), chat.js (send, regen, edit, branch), events.js (event bus + WebSocket), extensions.js (loader, registry, renderer pipeline, tool bridge), ui-core.js (DOM rendering + streaming), ui-format.js (markdown, code blocks), ui-primitives.js (shared components), ui-settings.js / ui-admin.js (settings and admin panels), notes.js, tokens.js, debug.js, settings-handlers.js, admin-handlers.js.
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_DRIVER |
postgres |
Database backend: postgres or sqlite |
DATABASE_URL |
|
SQLite: path to database file (e.g. /data/switchboard.db) |
DB_HOST |
localhost |
PostgreSQL host (ignored when DB_DRIVER=sqlite) |
DB_NAME |
chat_switchboard |
Database name (ignored when DB_DRIVER=sqlite) |
JWT_SECRET |
(required) | Token signing key |
SWITCHBOARD_ADMIN_USERNAME |
|
Bootstrap admin username |
SWITCHBOARD_ADMIN_PASSWORD |
|
Bootstrap admin password |
ENCRYPTION_KEY |
|
AES key for API key encryption (required if encrypted keys exist) |
SEED_USERS |
|
Dev/test only: user:pass:role,user2:pass2:role2 |
ENVIRONMENT |
development |
Environment name: development, test, or production. Auto-sets Gin release mode in production. |
GIN_MODE |
(auto) | Gin framework mode: debug, test, or release. Auto-derived from ENVIRONMENT if not set. |
STORAGE_BACKEND |
(auto) | pvc or s3. Auto-detects PVC if not set. |
STORAGE_PATH |
/data/storage |
PVC mount point (also scratch dir for S3 extraction) |
S3_ENDPOINT |
|
S3 endpoint (e.g. http://minio:9000, required for S3) |
S3_BUCKET |
|
S3 bucket name (must exist, required for S3) |
S3_ACCESS_KEY |
|
S3 access key ID |
S3_SECRET_KEY |
|
S3 secret access key |
S3_REGION |
us-east-1 |
S3 region |
S3_PREFIX |
|
Optional key prefix within bucket |
S3_FORCE_PATH_STYLE |
true |
Path-style URLs (required for MinIO, Ceph) |
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.11.0 server/
docker build -f Dockerfile.frontend -t your-registry/switchboard-fe:0.11.0 .
Backend deployment:
containers:
- name: api
image: your-registry/switchboard-api:0.11.0
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.11.0
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, DOMPurify, and KaTeX (JS + CSS + fonts) from npm during the vendor stage. Mermaid.js loads dynamically from local vendor with CDN fallback. No CDN calls at runtime in airgapped deployments. The src/vendor/ directory contains local copies as fallback for development without Docker.
Database
PostgreSQL (default)
PostgreSQL 16+ recommended. The pgcrypto and vector (pgvector) extensions are used for UUID generation and vector similarity search respectively.
SQLite (single-user / edge / dev)
Set DB_DRIVER=sqlite and DATABASE_URL=/path/to/switchboard.db to run with an embedded SQLite database. No external dependencies — the binary is self-contained (pure Go, no CGO). The SQLite backend has full feature parity with Postgres including knowledge base vector search, which uses app-level cosine similarity computed in Go rather than pgvector.
# Minimal single-binary startup
DB_DRIVER=sqlite DATABASE_URL=./data/switchboard.db JWT_SECRET=changeme ./switchboard
SQLite is best suited for single-user workstations, edge deployments, air-gapped laptops, and local development. For multi-user production with concurrent writes, use PostgreSQL.
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.19)
| Table | Purpose |
|---|---|
users |
Accounts with role, avatar, settings, encrypted vault (UEK) |
provider_configs |
API provider configurations (scope: global/team/personal) |
model_catalog |
Synced model list with capabilities, visibility, and type |
personas |
Model presets (scope: global/team/personal) |
projects |
Workspaces grouping channels, KBs, and notes (scope: personal/team/global) |
project_channels |
Ordered channel-to-project membership (UNIQUE channel_id) |
project_knowledge_bases |
KB-to-project binding with auto_search flag |
project_notes |
Note-to-project association |
channels |
Conversations with type (direct/group/channel), optional project_id |
messages |
Message tree with parent_id for forking, tool_calls JSONB |
teams / team_members |
Organizational units |
notes |
Markdown notes with full-text search |
knowledge_bases / kb_documents / kb_chunks |
RAG pipeline with pgvector embeddings |
extensions / extension_user_settings |
Browser extension registry and per-user config |
usage_log / model_pricing |
Token usage tracking and cost calculation |
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)
Projects
GET/POST /projects— List and create projectsGET/PUT/DELETE /projects/:id— Project CRUD (owner-only delete)POST/DELETE /projects/:id/channels/:channelId— Add/remove channelGET /projects/:id/channels— List project channelsPUT /projects/:id/channels/reorder— Reorder channelsPOST/DELETE /projects/:id/knowledge-bases/:kbId— Bind/unbind KBPOST/DELETE /projects/:id/notes/:noteId— Bind/unbind note
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/PUT /admin/roles/:role— Model role configurationGET /admin/usage— Usage dashboardGET /admin/audit— Audit logGET/POST/PUT/DELETE /admin/extensions— Extension management
Extensions
GET /extensions?tier=browser— List enabled browser extensionsGET /extensions/:id/assets/*path— Serve extension assets (public, no auth)
WebSocket
GET /ws?token=<jwt>— EventBus WebSocket for real-time events and tool bridge
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.