Changeset 0.17.2 (#77)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Architecture — Chat Switchboard v0.11
|
||||
# Architecture — Chat Switchboard v0.17
|
||||
|
||||
## Deployment Modes
|
||||
|
||||
@@ -8,9 +8,9 @@ Three Docker images support different deployment scenarios:
|
||||
|-------|-----------|----------|----------|
|
||||
| **Unified** | `Dockerfile` | nginx + Go backend | Dev, docker-compose, single-node |
|
||||
| **Backend** | `server/Dockerfile` | Go binary only | K8s — scale API independently |
|
||||
| **Frontend** | `Dockerfile.frontend` | nginx + static files | K8s — scale FE independently |
|
||||
| **Frontend** | `Dockerfile.frontend` | nginx + static files + CM6 bundle | K8s — scale FE independently |
|
||||
|
||||
**Unified** bundles everything in one container. Nginx serves static files and proxies `/api/*` and `/ws` to the Go backend running on `:8080`. Good for development and small deployments.
|
||||
**Unified** bundles everything in one container (4-stage Docker build: vendor libs → CM6 bundle → Go backend → nginx runtime). Nginx serves static files and proxies `/api/*` and `/ws` to the Go backend running on `:8080`. Good for development and small deployments.
|
||||
|
||||
**Split (Backend + Frontend)** for production K8s: Ingress routes `/api/*` and `/ws` to the backend Service, everything else to the frontend Service. The frontend entrypoint (`docker-entrypoint-fe.sh`) handles `BASE_PATH` injection into `index.html` and dynamic nginx config generation at startup. Supports branding volume mounts at `/branding/`.
|
||||
|
||||
@@ -29,8 +29,8 @@ Three Docker images support different deployment scenarios:
|
||||
└──────┬──────┘ └────────────────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ PostgreSQL │
|
||||
└─────────────┘
|
||||
│ PostgreSQL │ (or SQLite for
|
||||
└─────────────┘ single-node)
|
||||
```
|
||||
|
||||
## Design Principles
|
||||
@@ -39,7 +39,7 @@ Three Docker images support different deployment scenarios:
|
||||
|
||||
2. **Roles vs Teams**: Clean separation between vertical permissions (Roles: admin, user) and horizontal visibility (Teams: organizational units). A user's role determines what they can *do*; their team membership determines what they can *see*.
|
||||
|
||||
3. **Store Layer**: All database access goes through typed Go interfaces (`store.Stores`). Handlers never write raw SQL. This enables future portability (SQLite for dev, Postgres for prod) and testability (mock stores).
|
||||
3. **Store Layer**: All database access goes through typed Go interfaces (`store.Stores`). Handlers never write raw SQL. The store layer has two implementations — `store/postgres/` and `store/sqlite/` — selected at startup via `DB_DRIVER`. This enables Postgres for production and SQLite for single-node or air-gapped deployments.
|
||||
|
||||
4. **Scope Model**: Provider configs, personas, and model settings all use a three-value `scope` column: `global` (admin-managed, visible to all), `team` (team-admin-managed, visible to team), `personal` (user-managed, visible to owner). The `owner_id` column points to the owning user or team depending on scope.
|
||||
|
||||
@@ -112,37 +112,51 @@ server/
|
||||
├── main.go # Wiring: stores → handlers → routes
|
||||
├── config/config.go # Env-based configuration
|
||||
├── database/
|
||||
│ ├── database.go # Connection management
|
||||
│ ├── database.go # Connection management (PG + SQLite)
|
||||
│ ├── migrate.go # Auto-migration on startup
|
||||
│ └── migrations/
|
||||
│ └── 001_v09_schema.sql # Consolidated schema
|
||||
│ ├── 001_v016_schema.sql # Consolidated schema (PG)
|
||||
│ └── 002_v017_persona_kb.sql
|
||||
├── store/
|
||||
│ ├── interfaces.go # Store interfaces + shared types
|
||||
│ └── postgres/ # Postgres implementations
|
||||
│ ├── postgres/ # Postgres implementations (19 stores)
|
||||
│ │ ├── stores.go # NewStores() constructor
|
||||
│ │ ├── provider.go # ProviderStore
|
||||
│ │ ├── catalog.go # CatalogStore
|
||||
│ │ ├── persona.go # PersonaStore
|
||||
│ │ ├── channel.go # ChannelStore
|
||||
│ │ ├── message.go # MessageStore
|
||||
│ │ ├── user.go # UserStore
|
||||
│ │ ├── team.go # TeamStore
|
||||
│ │ ├── note.go # NoteStore
|
||||
│ │ ├── knowledge.go # KnowledgeStore
|
||||
│ │ └── ...
|
||||
│ └── sqlite/ # SQLite implementations (19 stores)
|
||||
│ ├── stores.go # NewStores() constructor
|
||||
│ ├── provider.go # ProviderStore
|
||||
│ ├── catalog.go # CatalogStore
|
||||
│ ├── persona.go # PersonaStore
|
||||
│ ├── user.go # UserStore
|
||||
│ ├── team.go # TeamStore
|
||||
│ ├── policy.go # PolicyStore
|
||||
│ ├── audit.go # AuditStore
|
||||
│ └── ...
|
||||
│ └── ... # Mirror of postgres/ with dialect adaptations
|
||||
├── models/models.go # Shared domain types
|
||||
├── capabilities/
|
||||
│ ├── intrinsic.go # Heuristic detection + resolution
|
||||
│ └── resolver.go # ModelsForUser() unified resolver
|
||||
├── compaction/ # Conversation summarization engine
|
||||
├── crypto/ # AES-256-GCM API key encryption
|
||||
├── events/ # EventBus + WebSocket hub
|
||||
├── extraction/ # Document text extraction pipeline
|
||||
├── handlers/ # HTTP handlers (Gin)
|
||||
│ ├── auth.go # Login, register, refresh, logout
|
||||
│ ├── admin.go # User/config/model management
|
||||
│ ├── channels.go # Channel CRUD
|
||||
│ ├── messages.go # Message CRUD + forking
|
||||
│ ├── completion.go # Chat completions (SSE streaming)
|
||||
│ ├── stream_loop.go # Shared streaming + tool execution loop
|
||||
│ ├── capabilities.go # Model list + ResolveModelCaps
|
||||
│ ├── presets.go # Persona CRUD (all scopes)
|
||||
│ ├── apiconfigs.go # User provider config CRUD (BYOK)
|
||||
│ ├── teams.go # Team management
|
||||
│ ├── notes.go # Notes CRUD + search
|
||||
│ ├── knowledge.go # Knowledge base management
|
||||
│ └── ...
|
||||
├── knowledge/ # KB chunking, embedding, search
|
||||
├── providers/ # LLM provider adapters
|
||||
│ ├── provider.go # Provider interface
|
||||
│ ├── anthropic.go
|
||||
@@ -150,8 +164,8 @@ server/
|
||||
│ ├── openrouter.go
|
||||
│ └── venice.go
|
||||
├── middleware/ # Auth, admin, CORS, rate limiting
|
||||
├── events/ # EventBus + WebSocket hub
|
||||
└── tools/ # Built-in tool definitions (notes)
|
||||
├── storage/ # Blob storage (PVC + S3)
|
||||
└── tools/ # Built-in tool definitions
|
||||
```
|
||||
|
||||
## Store Layer Pattern
|
||||
@@ -210,25 +224,80 @@ Used by: `provider_configs`, `personas`, `model_catalog` (visibility column adds
|
||||
|
||||
## Schema Migration
|
||||
|
||||
Single consolidated migration (`001_v09_schema.sql`) replaces the previous 21 incremental migrations. The Go backend auto-migrates on startup:
|
||||
Migrations are handled by the backend on startup (no separate migration job):
|
||||
|
||||
1. Creates `schema_migrations` table if absent
|
||||
2. Checks which migration files have been applied
|
||||
3. Applies any new `.sql` files in order
|
||||
|
||||
Postgres uses consolidated schemas (`001_v016_schema.sql` + incremental). SQLite uses application-generated UUIDs and `datetime()` instead of `gen_random_uuid()` and `now()`. Both drivers share the same migration tracking table.
|
||||
|
||||
## Frontend Architecture
|
||||
|
||||
Vanilla JavaScript, no build step. Five files with clear responsibilities:
|
||||
Vanilla JavaScript, no framework. The frontend ships as static files served by nginx. The only build step is the CM6 editor bundle, compiled at Docker build time via esbuild (IIFE output, no runtime bundler).
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `api.js` | HTTP client with token refresh. All backend calls. |
|
||||
| `app.js` | Application state machine. Business logic. |
|
||||
| `ui.js` | DOM rendering. All `document.createElement` calls. |
|
||||
| `events.js` | Labeled event bus with WebSocket bridge. |
|
||||
| `debug.js` | Admin debug panel (model list, stats, config). |
|
||||
### File Structure
|
||||
|
||||
Communication: `app.js` calls `API.*` methods, updates state, then calls `UI.*` methods to render. `events.js` handles real-time updates via WebSocket. No framework, no virtual DOM, no reactive bindings.
|
||||
```
|
||||
src/
|
||||
├── index.html # Single-page app shell
|
||||
├── sw.js # Service worker (offline cache)
|
||||
├── manifest.json # PWA manifest
|
||||
├── css/styles.css # All styles (CSS variables, light/dark themes)
|
||||
├── js/
|
||||
│ ├── api.js # HTTP client with token refresh
|
||||
│ ├── app.js # Application state machine, startup, routing
|
||||
│ ├── chat.js # Chat input abstraction, message send/receive
|
||||
│ ├── events.js # Labeled event bus + WebSocket bridge
|
||||
│ ├── debug.js # Debug panel, diagnostics, state export
|
||||
│ ├── ui-core.js # DOM rendering, sidebar, modals, panels
|
||||
│ ├── ui-settings.js # Settings tabs, theme, appearance, teams
|
||||
│ ├── ui-admin.js # Admin panel rendering
|
||||
│ ├── admin-handlers.js # Admin CRUD operations + extension editors
|
||||
│ ├── settings-handlers.js # User settings CRUD, command palette
|
||||
│ ├── tokens.js # Input token counting + context budget
|
||||
│ ├── attachments.js # File upload, paste-to-file, lightbox
|
||||
│ ├── notes.js # Notes panel CRUD
|
||||
│ ├── knowledge.js # Knowledge base UI
|
||||
│ └── __tests__/ # Node.js test suite (node --test)
|
||||
├── editor/ # CM6 source (compiled at build time)
|
||||
│ ├── package.json # CM6 + language mode dependencies
|
||||
│ ├── package-lock.json # Lockfile for npm ci
|
||||
│ ├── build.mjs # esbuild script → IIFE bundle
|
||||
│ ├── index.mjs # Bundle entrypoint (window.CM)
|
||||
│ ├── chat-input.mjs # Chat input factory (markdown, code blocks)
|
||||
│ ├── code-editor.mjs # Code editor factory (syntax, keybindings)
|
||||
│ └── theme.mjs # Switchboard + chat input themes
|
||||
└── vendor/ # Vendored libraries (local + CDN fallback)
|
||||
├── marked/ # Markdown renderer
|
||||
├── purify/ # DOMPurify (XSS protection)
|
||||
├── mermaid/ # Diagram renderer
|
||||
├── katex/ # Math renderer
|
||||
└── codemirror/ # CM6 bundle (built by Docker)
|
||||
└── codemirror.bundle.js # ~295KB min, ~90KB gzip
|
||||
```
|
||||
|
||||
### CM6 Integration
|
||||
|
||||
The CodeMirror 6 bundle provides two factory functions exposed on `window.CM`:
|
||||
|
||||
**`CM.chatInput(target, opts)`** — Markdown-mode editor for the chat input area. Features: auto-growing height, Enter=send / Shift+Enter=newline, WYSIWYG fenced code block decorations (visual container with monospace font and accent border), inline code shortcut (Ctrl/Cmd+E), spell check. Always uses standard keybindings.
|
||||
|
||||
**`CM.codeEditor(target, opts)`** — Full-featured code editor for the admin extension panel (JSON manifests, JavaScript scripts). Features: line numbers, bracket matching, search/replace, fold gutter, 10 bundled language modes. Supports Vim and Emacs keybindings via user preference (reconfigurable at runtime via compartments).
|
||||
|
||||
Both factories return a clean API: `getValue()`, `setValue()`, `focus()`, `destroy()`. The `ChatInput` abstraction in `chat.js` wraps the CM6 instance with a textarea fallback — all callsites use `ChatInput.getValue()` etc., never raw DOM access.
|
||||
|
||||
**Graceful degradation**: Every integration point checks `window.CM` before using CM6. If the bundle fails to load (air-gapped without the build step, CDN issues), the app falls back to native `<textarea>` with zero breakage.
|
||||
|
||||
### Theme System
|
||||
|
||||
CSS variables define the color palette in `:root` (dark, default) and `[data-theme="light"]` (light override). All UI components — including CM6 editors — reference these variables, so theme switches propagate instantly without editor reconfiguration.
|
||||
|
||||
The appearance settings offer three modes: Light, Dark, System. System mode listens to `prefers-color-scheme` and re-evaluates on OS theme change. Theme changes emit `theme.changed` on the EventBus; CM6 code editors toggle the `oneDark` syntax theme via compartment reconfiguration.
|
||||
|
||||
### Communication Pattern
|
||||
|
||||
`app.js` calls `API.*` methods, updates state, then calls `UI.*` methods to render. `events.js` handles real-time updates via WebSocket. No framework, no virtual DOM, no reactive bindings.
|
||||
|
||||
## Security Model
|
||||
|
||||
|
||||
Reference in New Issue
Block a user