Changeset 0.9.0 (#50)
This commit is contained in:
334
README.md
334
README.md
@@ -1,192 +1,222 @@
|
||||
# 🔀 Chat Switchboard
|
||||
# Chat Switchboard
|
||||
|
||||
**Multi-Model AI Chat Platform — Self-Hosted, Extensible, Fast**
|
||||
|
||||
A self-hosted AI chat interface that routes conversations across multiple LLM providers. Built with a Go backend for performance and a clean vanilla JS frontend inspired by Open WebUI.
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://go.dev/)
|
||||
|
||||
---
|
||||
|
||||
## What It Does
|
||||
|
||||
- **Multi-provider routing** — Connect OpenAI, Anthropic, Venice.ai, Ollama, or any OpenAI-compatible API. Switch models per-conversation.
|
||||
- **Streaming responses** — SSE-based streaming with stop button, thinking block display, and full markdown rendering.
|
||||
- **Per-user API keys** — Each user manages their own provider keys. Admins can set global keys shared across the instance.
|
||||
- **Admin panel** — User management, global provider config, model visibility, registration toggle, usage stats.
|
||||
- **Self-hosted** — Single Docker image, PostgreSQL backend. No external dependencies at runtime.
|
||||
|
||||
---
|
||||
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
|
||||
|
||||
### Docker (Recommended)
|
||||
|
||||
```bash
|
||||
git clone https://git.gobha.me/xcaliber/chat-switchboard.git
|
||||
cd chat-switchboard
|
||||
cp server/.env.example server/.env
|
||||
# Edit .env: set DATABASE_URL, JWT_SECRET, etc.
|
||||
docker-compose up -d
|
||||
# Clone and start
|
||||
git clone <repo-url> && cd chat-switchboard
|
||||
docker compose up -d
|
||||
|
||||
# Access at http://localhost:3000
|
||||
# Default admin: admin / admin
|
||||
```
|
||||
|
||||
Access at `http://localhost:3000`. First registered user becomes admin.
|
||||
## Features
|
||||
|
||||
### Manual
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
cd server
|
||||
cp .env.example .env
|
||||
go build -o switchboard .
|
||||
./switchboard
|
||||
|
||||
# Frontend — serve src/ with any HTTP server
|
||||
cd ../src
|
||||
python3 -m http.server 8080
|
||||
```
|
||||
|
||||
Point the frontend at the backend by setting the API base URL (defaults to same-origin).
|
||||
|
||||
---
|
||||
- **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
|
||||
|
||||
```
|
||||
┌──────────────────────────┐
|
||||
│ Frontend (Vanilla JS) │
|
||||
│ 4 files + vendor libs │
|
||||
│ No build step required │
|
||||
└───────────┬──────────────┘
|
||||
│ REST + SSE
|
||||
▼
|
||||
┌──────────────────────────┐
|
||||
│ Go Backend │
|
||||
│ ├── Auth (JWT + refresh)│
|
||||
│ ├── Chat CRUD │
|
||||
│ ├── Completion proxy │
|
||||
│ ├── Provider management │
|
||||
│ ├── Admin endpoints │
|
||||
│ └── Static file server │
|
||||
└───────────┬──────────────┘
|
||||
│
|
||||
PostgreSQL
|
||||
┌─────────────┐ ┌──────────────────────────────┐
|
||||
│ Browser │────▶│ nginx (port 80) │
|
||||
│ (vanilla JS)│◀────│ ├─ /api/* → Go backend:8080 │
|
||||
└─────────────┘ │ ├─ /ws → WebSocket proxy │
|
||||
│ └─ /* → static files │
|
||||
└──────────────────────────────┘
|
||||
│
|
||||
┌─────────▼─────────┐
|
||||
│ Go Backend │
|
||||
│ ├─ handlers/ │
|
||||
│ ├─ store/postgres/ │
|
||||
│ ├─ providers/ │
|
||||
│ └─ capabilities/ │
|
||||
└─────────┬─────────┘
|
||||
│
|
||||
┌─────────▼─────────┐
|
||||
│ PostgreSQL 16 │
|
||||
└───────────────────┘
|
||||
```
|
||||
|
||||
### Frontend (src/)
|
||||
**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.
|
||||
|
||||
| File | Size | Purpose |
|
||||
|------|------|---------|
|
||||
| `api.js` | 240 lines | HTTP client, token management, auto-refresh on 401 |
|
||||
| `app.js` | 490 lines | State, init flow, auth, chat CRUD, event wiring |
|
||||
| `ui.js` | 510 lines | DOM rendering, streaming, modals, formatting |
|
||||
| `debug.js` | 550 lines | Console/network intercept, state inspector (Ctrl+Shift+L) |
|
||||
| `vendor/` | 62KB | marked.js + DOMPurify (local, CDN fallback) |
|
||||
|
||||
No framework. No build step. No node_modules. Works in disconnected environments with vendor libs baked in.
|
||||
|
||||
### Backend (server/)
|
||||
|
||||
Go 1.22, ~26 source files. Key packages:
|
||||
|
||||
- `handlers/` — Auth, chats, messages, completions, API configs, admin
|
||||
- `middleware/` — JWT auth, admin role check, rate limiting, error handling, logging
|
||||
- `providers/` — OpenAI and Anthropic adapters with streaming support
|
||||
- `models/` — Database models
|
||||
- `database/` — PostgreSQL connection + migration runner
|
||||
- `config/` — Environment-based configuration
|
||||
|
||||
### API Surface
|
||||
|
||||
| Route | Method | Auth | Description |
|
||||
|-------|--------|------|-------------|
|
||||
| `/health` | GET | — | Health check + version |
|
||||
| `/api/v1/auth/register` | POST | — | Create account |
|
||||
| `/api/v1/auth/login` | POST | — | Get JWT tokens |
|
||||
| `/api/v1/auth/refresh` | POST | — | Rotate access token |
|
||||
| `/api/v1/chats` | GET/POST | ✓ | List / create chats |
|
||||
| `/api/v1/chats/:id` | GET/PUT/DELETE | ✓ | Chat CRUD |
|
||||
| `/api/v1/chats/:id/messages` | GET | ✓ | Message history |
|
||||
| `/api/v1/chat/completions` | POST | ✓ | Streaming SSE or sync JSON |
|
||||
| `/api/v1/models/enabled` | GET | ✓ | Available models |
|
||||
| `/api/v1/api-configs` | GET/POST/DELETE | ✓ | User provider management |
|
||||
| `/api/v1/profile` | GET/PUT | ✓ | User profile |
|
||||
| `/api/v1/settings` | GET/PUT | ✓ | User settings (persisted) |
|
||||
| `/api/v1/admin/*` | various | admin | User/provider/model/settings management |
|
||||
|
||||
---
|
||||
**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 via environment variables (see `server/.env.example`):
|
||||
All configuration via environment variables. See `server/.env.example` for the full list.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `PORT` | `8080` | Backend listen port |
|
||||
| `DATABASE_URL` | — | PostgreSQL connection string |
|
||||
| `JWT_SECRET` | — | Token signing key |
|
||||
| `JWT_EXPIRY` | `15m` | Access token TTL |
|
||||
| `REFRESH_EXPIRY` | `7d` | Refresh token TTL |
|
||||
| `CORS_ORIGINS` | `*` | Allowed origins |
|
||||
| `REGISTRATION_ENABLED` | `true` | Allow new signups |
|
||||
|
||||
---
|
||||
| `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
|
||||
|
||||
### Docker Compose (unified)
|
||||
Three Docker images support different scenarios:
|
||||
|
||||
The provided `Dockerfile` is a 3-stage build:
|
||||
1. `golang:1.22-bookworm` — compiles Go backend
|
||||
2. `node:20-alpine` — downloads vendor JS libs via `npm pack`
|
||||
3. `nginx:1-alpine` — serves frontend, proxies `/api/` to Go backend
|
||||
| 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 |
|
||||
|
||||
Vendor libs are baked into the image at build time — no CDN access needed at runtime.
|
||||
### Docker Compose (development — unified image)
|
||||
|
||||
### Reverse Proxy
|
||||
```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
|
||||
```
|
||||
|
||||
If running behind nginx/Caddy, proxy `/api/` and `/health` to the Go backend. Serve `src/` as static files.
|
||||
### Kubernetes (split images)
|
||||
|
||||
---
|
||||
Build and push both images:
|
||||
|
||||
```bash
|
||||
docker build -f server/Dockerfile -t your-registry/switchboard-api:0.9.0 server/
|
||||
docker build -f Dockerfile.frontend -t your-registry/switchboard-fe:0.9.0 .
|
||||
```
|
||||
|
||||
**Backend deployment:**
|
||||
|
||||
```yaml
|
||||
containers:
|
||||
- name: api
|
||||
image: your-registry/switchboard-api:0.9.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:**
|
||||
|
||||
```yaml
|
||||
containers:
|
||||
- name: frontend
|
||||
image: your-registry/switchboard-fe:0.9.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` 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 <token>` 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
|
||||
- `GET/PUT /models/preferences` — User model visibility settings
|
||||
|
||||
### 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 (hot reload with air)
|
||||
# Backend (requires Go 1.22+)
|
||||
cd server
|
||||
go install github.com/air-verse/air@latest
|
||||
air
|
||||
cp .env.example .env # edit with your DB credentials
|
||||
go run .
|
||||
|
||||
# Frontend — just edit files, hard-refresh browser
|
||||
# Debug: Ctrl+Shift+L opens debug modal
|
||||
# Frontend (just serve static files)
|
||||
# Use any HTTP server pointed at src/
|
||||
python3 -m http.server 3000 --directory src
|
||||
```
|
||||
|
||||
### Database Migrations
|
||||
|
||||
Migrations in `migrations/` run automatically on startup. Current schema:
|
||||
- `001_full_schema.sql` — users, chats, messages, api_configs
|
||||
- `002_refresh_tokens.sql` — token rotation
|
||||
- `003_global_settings.sql` — admin settings table
|
||||
- `004_model_configs.sql` — per-model enable/disable
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
|
||||
See [ROADMAP.md](ROADMAP.md) for the full plan. Next up:
|
||||
|
||||
1. **WebSocket hub** — real-time message delivery, typing indicators
|
||||
2. **Channels** — multi-user + AI chat rooms with @mentions
|
||||
3. **Plugin system** — Go-native extensions, installable via admin UI
|
||||
4. **Notes & Knowledge Base** — markdown notes, document upload, RAG via pgvector
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT — build anything, including commercial products.
|
||||
|
||||
---
|
||||
|
||||
**Repository:** https://git.gobha.me/xcaliber/chat-switchboard
|
||||
Proprietary. All rights reserved.
|
||||
|
||||
Reference in New Issue
Block a user