Changeset 0.5.0 (#35)

This commit is contained in:
2026-02-19 15:03:20 +00:00
parent a93a6b9635
commit 30d0c11219
65 changed files with 5345 additions and 8070 deletions

View File

@@ -1,482 +0,0 @@
# 🏗️ Chat Switchboard Architecture
## Overview
Chat Switchboard is a **plugin-first, multi-model AI platform** with dual-mode operation:
```
┌─────────────────────────────────────────────────────┐
│ FRONTEND (Vanilla JS) │
│ ├─ Managed Mode: Full backend features + auth │
│ └─ Unmanaged Mode: LocalStorage only, offline │
└─────────────────────┬───────────────────────────────┘
┌────────────┴────────────┐
│ │
[HTTP/WS] [LocalStorage Only]
┌────────▼─────────────────────────────────────────────┐
│ BACKEND (Go + PostgreSQL) │
│ ├─ Core API (minimal, orchestration only) │
│ ├─ Extension Manager │
│ └─ WebSocket Server (real-time features) │
└────────┬─────────────────────────────────────────────┘
├─ PostgreSQL (state, users, data)
└─ Extensions (modular features)
├─ Chat Engine (Python)
├─ Channels (Go)
├─ RAG Engine (Python)
├─ Workflow Builder (Go/Python)
└─ Custom Extensions...
```
## Operating Modes
### 🏠 Unmanaged Mode (LocalStorage)
**Use Case:** Personal use, offline, privacy-focused
- ✅ No backend required
- ✅ No account/auth needed
- ✅ Works offline
- ✅ All data in browser LocalStorage
- ✅ Export/import for backup
- ❌ No multi-device sync
- ❌ No collaboration features
- ❌ No server-side extensions
**Detection:**
```javascript
// src/js/state.js
State.mode = State.settings.backendUrl ? 'managed' : 'unmanaged';
```
### 🌐 Managed Mode (Full Backend)
**Use Case:** Teams, collaboration, advanced features
- ✅ Multi-user with authentication
- ✅ Real-time collaboration (WebSockets)
- ✅ Server-side extensions (Python, Go)
- ✅ Shared knowledge bases
- ✅ Workflow orchestration
- ✅ Usage analytics
- ❌ Requires backend deployment
**Switching:**
```javascript
// User configures backend URL in settings
State.settings.backendUrl = 'https://api.chatswitch.example.com';
State.settings.backendToken = 'jwt_token_here';
```
## Core Features (All Modes)
### 1. Chat (User → LLM)
- Per-conversation model selection
- Streaming responses
- Message history
- Export (Markdown, JSON, Plain Text)
**Managed Mode Additions:**
- Multi-model auto-routing
- Cost tracking
- Shared chats
- Server-side tool calling
### 2. Channels (User → User + AI)
**Managed Mode Only**
- Public/private channels
- @mentions for users and AI models
- Threaded conversations
- Reactions
- Real-time updates (WebSocket)
**Example:**
```
@alice What do you think about this design?
@claude-3.5 Can you review this code?
```
### 3. Notes & Knowledge Bases
**Both Modes:**
- Personal notes
- Markdown editing
- Tagging and folders
**Managed Mode Additions:**
- Shared notes
- Knowledge base collections
- RAG (Retrieval Augmented Generation)
- Vector embeddings (pgvector)
- Semantic search
### 4. Workflows (🌟 UNIQUE FEATURE)
**Managed Mode Only**
Visual workflow builder for multi-step AI operations:
```
[User Input] → [Model A: Research] → [Model B: Summarize] → [Tool: Save to KB] → [Output]
```
**Use Cases:**
- Research pipelines (search → summarize → extract → store)
- Multi-model consensus (run prompt through 3 models, compare)
- Data processing (extract → transform → analyze → report)
- Content creation (outline → draft → edit → format)
**Why It's Unique:**
- Competitors have single-shot chat
- This enables **AI composition** - chain multiple models and tools
- Shareable templates (marketplace potential)
- Visual no-code builder
## Extension Architecture
### Extension Types
#### 1. Frontend Extensions (UI Only)
**Both Modes** - Pure JavaScript, no backend needed
```javascript
// extensions/token-counter/main.js
window.ChatSwitchboard.registerExtension({
name: 'token-counter',
hooks: {
onMessageSend: (message) => {
const tokens = estimateTokens(message);
showToast(`~${tokens} tokens`);
}
}
});
```
#### 2. Backend Extensions (Full Power)
**Managed Mode Only** - Python, Go, Node.js
```
extensions/
├── web-search/
│ ├── extension.json # Manifest
│ ├── main.py # Python service
│ └── requirements.txt
```
**Communication:** HTTP/gRPC between Go core and extension processes
### Extension Protocol
```json
// extension.json
{
"name": "web-search",
"version": "1.0.0",
"runtime": "python",
"entry": "main.py",
"port": 9001,
"capabilities": ["tool"],
"tools": [
{
"name": "search_web",
"description": "Search the web for information",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"}
},
"required": ["query"]
}
}
]
}
```
## Technology Stack
### Frontend
- **Vanilla JavaScript** - No framework bloat
- **CSS3** - Custom dark theme
- **LocalStorage** - Offline-first
- **WebSocket** - Real-time (managed mode)
### Backend (Managed Mode)
- **Go** - Core API, routing, orchestration
- **PostgreSQL** - Primary data store
- **pgvector** - Vector embeddings for RAG
- **Redis** - Session cache, WebSocket pub/sub (optional)
- **Python** - AI/ML extensions (LangChain, embeddings)
### Infrastructure
- **Docker** - Easy deployment
- **nginx** - Reverse proxy
- **Let's Encrypt** - TLS certificates
## Data Flow
### Unmanaged Mode
```
User Input → Frontend State → LocalStorage
API Provider (OpenAI, etc)
Frontend State → LocalStorage
```
### Managed Mode
```
User Input → Frontend → WebSocket → Backend
PostgreSQL
Extension Manager
↓ ↓
[Chat Engine] [RAG Engine]
↓ ↓
Model Router → API Provider
WebSocket → Frontend
```
## WebSocket Protocol
### Events (Managed Mode)
```javascript
// Client → Server
{
type: 'chat.send',
chatId: 'uuid',
message: 'Hello',
model: 'gpt-4'
}
// Server → Client (streaming)
{
type: 'chat.stream',
chatId: 'uuid',
delta: 'Hello! ',
done: false
}
// Channel messages
{
type: 'channel.message',
channelId: 'uuid',
content: '@claude What do you think?',
mentions: {users: [], models: ['claude-3.5']}
}
// AI response in channel
{
type: 'channel.ai_response',
channelId: 'uuid',
model: 'claude-3.5',
content: 'I think...',
inReplyTo: 'message_uuid'
}
```
### Connection Management
```go
// server/websocket/hub.go
type Hub struct {
clients map[*Client]bool
broadcast chan Message
register chan *Client
unregister chan *Client
}
```
## Security
### Unmanaged Mode
- All sensitive data (API keys) in browser localStorage
- User responsible for backups
- No server-side attack surface
### Managed Mode
- **Authentication:** JWT tokens
- **Authorization:** RBAC (roles: user, admin, moderator)
- **API Keys:** Encrypted at rest (pgcrypto)
- **Rate Limiting:** Per-user, per-endpoint
- **CORS:** Strict origin checking
- **Webhooks:** HMAC signature verification
- **Extensions:** Sandboxed execution
## Deployment
### Unmanaged Mode
```bash
# Build standalone
./build.sh
# Serve
python3 -m http.server 8080 --directory standalone
# or upload index.html to any static host
```
### Managed Mode
```bash
# Docker Compose
docker-compose up -d
# Services:
# - postgres:5432
# - backend:8080
# - redis:6379 (optional)
# - nginx:443
```
## Scaling
### Horizontal Scaling (Managed)
```
┌─ Load Balancer (nginx)
├─ Backend Instance 1 ─┐
├─ Backend Instance 2 ─┼─ PostgreSQL (primary)
└─ Backend Instance 3 ─┘
Redis (WebSocket sync)
```
### Extension Scaling
- Extensions are separate processes
- Can run on different machines
- Service discovery via extension registry
## Plugin Ecosystem (🌟 DIFFERENTIATOR)
### Core Principle
**Everything is a plugin** (except minimal routing core)
| Feature | Implementation |
|---------|---------------|
| Chat | `extensions/chat-engine/` |
| Channels | `extensions/channels/` |
| Notes | `extensions/notes/` |
| Knowledge Bases | `extensions/rag-engine/` |
| Workflows | `extensions/workflows/` |
### Why This Matters
1. **Proof of Extensibility** - If core features work as plugins, any feature can
2. **Community Innovation** - Users build missing features
3. **Customization** - Disable unwanted features
4. **Marketplace Potential** - Monetize premium plugins
### Plugin Types
#### Official Plugins (Bundled)
- Chat Engine
- Channels
- RAG/Knowledge
- Web Search
- Code Execution
#### Community Plugins (Marketplace)
- Specialized tools
- Industry-specific workflows
- Integration plugins (Slack, Discord, etc)
- Custom AI models
#### Enterprise Plugins (Premium)
- SSO/SAML
- Advanced analytics
- Compliance logging
- Custom model hosting
## Unique Features Summary
### 1. Workflow Builder (⭐⭐⭐)
**What:** Visual DAG builder for multi-step AI operations
**Why Unique:** No competitor has AI orchestration at this level
**Use Case:** Research assistant = [Search] → [GPT-4 summarize] → [Claude verify] → [Save to KB]
### 2. Dual-Mode Operation (⭐⭐)
**What:** Works offline (LocalStorage) or managed (full backend)
**Why Unique:** Privacy-first with optional collaboration
**Use Case:** Personal use → upgrade to team without migration
### 3. Plugin-First Architecture (⭐⭐⭐)
**What:** Core features ARE plugins (dogfooding)
**Why Unique:** Proves extensibility, enables marketplace
**Use Case:** Build custom enterprise workflows as plugins
### 4. Multi-Model Auto-Routing (⭐⭐)
**What:** Automatically route to best/cheapest model
**Why Unique:** Most tools lock you to one provider
**Use Case:** Simple questions → GPT-3.5 ($), complex → GPT-4 ($$$)
### 5. Channels with AI Participants (⭐)
**What:** Slack-like channels where you @mention AI models
**Why Unique:** Collaborative AI + human conversations
**Use Case:** Team discusses design, @claude gives input, @gpt4 suggests alternatives
## Comparison Matrix
| Feature | Chat Switchboard | Open WebUI | ChatGPT | Claude Desktop |
|---------|-----------------|------------|---------|----------------|
| Multi-Model | ✅ | ✅ | ❌ | ❌ |
| Plugin System | ✅⭐ | Limited | ❌ | ❌ |
| Offline Mode | ✅ | ❌ | ❌ | Limited |
| Workflows | ✅⭐ | ❌ | ❌ | ❌ |
| Channels | ✅ | ✅ | ❌ | ❌ |
| RAG/Knowledge | ✅ | ✅ | ❌ | Limited |
| Self-Hosted | ✅ | ✅ | ❌ | ❌ |
| Open Source | ✅ | ✅ | ❌ | ❌ |
**Competitive Advantage:** Workflows + Plugin Architecture + Dual-Mode
## Roadmap
### Phase 1: Foundation (Weeks 1-4)
- ✅ Frontend core
- ⬜ Backend API skeleton
- ⬜ PostgreSQL integration
- ⬜ WebSocket server
- ⬜ Extension manager
### Phase 2: Core Features (Weeks 5-8)
- ⬜ Chat engine (as plugin)
- ⬜ Channels (as plugin)
- ⬜ Notes (as plugin)
- ⬜ Basic auth/users
### Phase 3: Unique Features (Weeks 9-12)
- ⬜ RAG/Knowledge bases
- ⬜ Workflow builder (visual editor)
- ⬜ Model auto-routing
- ⬜ Extension marketplace basics
### Phase 4: Polish (Weeks 13-16)
- ⬜ Desktop app (Tauri)
- ⬜ Mobile-responsive
- ⬜ Documentation
- ⬜ Example plugins (10+)
- ⬜ Launch 🚀
## Contributing
Since core features are plugins, contributors can:
1. Build new extensions (any language)
2. Improve existing plugins (PRs welcome)
3. Share workflows (template marketplace)
4. Report bugs, suggest features
**Plugin Development:**
```bash
# Use template
cp -r extensions/_template-python extensions/my-plugin
cd extensions/my-plugin
# Edit extension.json, main.py
python main.py # Runs on port from manifest
```
## License
MIT - Build whatever you want, including commercial
---
**Questions?** See `/docs` for detailed guides or join discussions.

View File

@@ -1,698 +0,0 @@
# ⚙️ Backend Features & Extensions Architecture
## Overview
This document defines the **server-side architecture** for Chat Switchboard, focusing on persistent storage, multi-model routing, function/tool calling, and backend extension capabilities.
---
## 🏗️ Core Backend Features
### 1. **Multi-Model Router**
Route requests to different LLM providers based on model, capabilities, cost, or custom logic.
#### Architecture
```
┌─────────────────┐
│ Frontend │
└────────┬────────┘
│ OpenAI-compatible API
┌────────▼────────────────────────────────────────┐
│ Chat Switchboard Server │
│ ┌──────────────────────────────────────────┐ │
│ │ Model Router │ │
│ │ - Route selection logic │ │
│ │ - Fallback handling │ │
│ │ - Load balancing │ │
│ └──────────┬───────────────────────────────┘ │
│ │ │
│ ┌──────────▼───────────────────────────────┐ │
│ │ Provider Adapters │ │
│ ├──────────────────────────────────────────┤ │
│ │ OpenAI │ Anthropic │ Local │ Custom │ │
│ └──────┬─────┴─────┬─────┴───┬───┴────┬────┘ │
└─────────┼───────────┼─────────┼────────┼───────┘
│ │ │ │
OpenAI API Claude API Ollama Custom
```
#### Implementation (`server/router/model_router.go`)
```go
type ModelRouter struct {
providers map[string]Provider
config *RouterConfig
}
type Provider interface {
Name() string
Models() []string
ChatCompletion(ctx context.Context, req *ChatRequest) (*ChatResponse, error)
StreamCompletion(ctx context.Context, req *ChatRequest) (<-chan *ChatChunk, error)
SupportsTools() bool
}
type RouterConfig struct {
DefaultProvider string
FallbackChain []string
ModelMapping map[string]string // model -> provider
LoadBalancing bool
CostOptimization bool
}
func (r *ModelRouter) Route(model string, req *ChatRequest) (Provider, error) {
// 1. Check explicit mapping
if provider, ok := r.config.ModelMapping[model]; ok {
return r.providers[provider], nil
}
// 2. Query each provider for model support
for _, provider := range r.providers {
if provider.SupportsModel(model) {
return provider, nil
}
}
// 3. Fallback chain
if len(r.config.FallbackChain) > 0 {
return r.providers[r.config.FallbackChain[0]], nil
}
return nil, ErrModelNotFound
}
```
---
### 2. **Tool/Function Calling System**
Enable LLMs to call external functions and tools.
#### Tool Registry (`server/tools/registry.go`)
```go
type Tool struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters map[string]interface{} `json:"parameters"`
Handler ToolHandler
}
type ToolHandler func(ctx context.Context, args map[string]interface{}) (interface{}, error)
type ToolRegistry struct {
tools map[string]*Tool
mu sync.RWMutex
}
func (r *ToolRegistry) Register(tool *Tool) {
r.mu.Lock()
defer r.mu.Unlock()
r.tools[tool.Name] = tool
}
func (r *ToolRegistry) Execute(name string, args map[string]interface{}) (interface{}, error) {
r.mu.RLock()
tool, ok := r.tools[name]
r.mu.RUnlock()
if !ok {
return nil, ErrToolNotFound
}
return tool.Handler(context.Background(), args)
}
func (r *ToolRegistry) GetOpenAIFunctions() []map[string]interface{} {
// Convert tools to OpenAI function calling format
functions := []map[string]interface{}{}
for _, tool := range r.tools {
functions = append(functions, map[string]interface{}{
"name": tool.Name,
"description": tool.Description,
"parameters": tool.Parameters,
})
}
return functions
}
```
#### Built-in Tools
```go
// Web Search Tool
func init() {
toolRegistry.Register(&Tool{
Name: "web_search",
Description: "Search the web for current information",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]interface{}{
"type": "string",
"description": "Search query",
},
},
"required": []string{"query"},
},
Handler: func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
query := args["query"].(string)
// Implement web search (DuckDuckGo, Google, etc.)
results := performWebSearch(query)
return results, nil
},
})
}
// Code Execution Tool
func init() {
toolRegistry.Register(&Tool{
Name: "execute_code",
Description: "Execute code in a sandboxed environment",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"language": map[string]string{
"type": "string",
"enum": []string{"python", "javascript", "bash"},
},
"code": map[string]string{
"type": "string",
},
},
"required": []string{"language", "code"},
},
Handler: func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
lang := args["language"].(string)
code := args["code"].(string)
// Execute in Docker container or isolated runtime
result := executeSandboxed(lang, code)
return result, nil
},
})
}
```
#### Function Calling Flow
```go
func (h *ChatHandler) HandleFunctionCalling(req *ChatRequest) (*ChatResponse, error) {
// 1. Send initial request with tools
req.Tools = h.toolRegistry.GetOpenAIFunctions()
resp, err := h.provider.ChatCompletion(req)
// 2. Check if model wants to call a function
if resp.FunctionCall != nil {
// 3. Execute the function
result, err := h.toolRegistry.Execute(
resp.FunctionCall.Name,
resp.FunctionCall.Arguments,
)
// 4. Send function result back to model
req.Messages = append(req.Messages, Message{
Role: "function",
Name: resp.FunctionCall.Name,
Content: jsonStringify(result),
})
// 5. Get final response
return h.provider.ChatCompletion(req)
}
return resp, nil
}
```
---
### 3. **Backend Extension System**
Plugin architecture for server-side extensions.
#### Extension Interface (`server/extensions/extension.go`)
```go
type Extension interface {
ID() string
Name() string
Version() string
// Lifecycle
Initialize(ctx context.Context, config map[string]interface{}) error
Shutdown() error
// Hooks
OnChatRequest(ctx context.Context, req *ChatRequest) error
OnChatResponse(ctx context.Context, resp *ChatResponse) error
OnToolCall(ctx context.Context, tool string, args map[string]interface{}) error
// Optional: Provide custom tools
RegisterTools() []*Tool
// Optional: Provide custom routes
RegisterRoutes(router *gin.Engine)
}
type BaseExtension struct {
id string
name string
version string
}
func (e *BaseExtension) ID() string { return e.id }
func (e *BaseExtension) Name() string { return e.name }
func (e *BaseExtension) Version() string { return e.version }
// Default no-op implementations
func (e *BaseExtension) Initialize(ctx context.Context, config map[string]interface{}) error {
return nil
}
func (e *BaseExtension) Shutdown() error { return nil }
func (e *BaseExtension) OnChatRequest(ctx context.Context, req *ChatRequest) error {
return nil
}
func (e *BaseExtension) OnChatResponse(ctx context.Context, resp *ChatResponse) error {
return nil
}
func (e *BaseExtension) OnToolCall(ctx context.Context, tool string, args map[string]interface{}) error {
return nil
}
func (e *BaseExtension) RegisterTools() []*Tool { return nil }
func (e *BaseExtension) RegisterRoutes(router *gin.Engine) {}
```
#### Extension Manager
```go
type ExtensionManager struct {
extensions map[string]Extension
mu sync.RWMutex
}
func (m *ExtensionManager) Load(ext Extension, config map[string]interface{}) error {
if err := ext.Initialize(context.Background(), config); err != nil {
return err
}
m.mu.Lock()
m.extensions[ext.ID()] = ext
m.mu.Unlock()
// Register any tools provided by extension
for _, tool := range ext.RegisterTools() {
toolRegistry.Register(tool)
}
return nil
}
func (m *ExtensionManager) ExecuteHook(hookName string, args ...interface{}) error {
m.mu.RLock()
defer m.mu.RUnlock()
for _, ext := range m.extensions {
switch hookName {
case "OnChatRequest":
if err := ext.OnChatRequest(args[0].(context.Context), args[1].(*ChatRequest)); err != nil {
return err
}
case "OnChatResponse":
if err := ext.OnChatResponse(args[0].(context.Context), args[1].(*ChatResponse)); err != nil {
return err
}
}
}
return nil
}
```
---
### 4. **Example Backend Extensions**
#### A. Logging Extension
```go
type LoggingExtension struct {
BaseExtension
db *sql.DB
}
func NewLoggingExtension() *LoggingExtension {
return &LoggingExtension{
BaseExtension: BaseExtension{
id: "logging",
name: "Request Logging",
version: "1.0.0",
},
}
}
func (e *LoggingExtension) OnChatRequest(ctx context.Context, req *ChatRequest) error {
log.Printf("[CHAT] Model: %s, Messages: %d", req.Model, len(req.Messages))
// Store in database
_, err := e.db.Exec(`
INSERT INTO request_logs (user_id, model, message_count, timestamp)
VALUES ($1, $2, $3, NOW())
`, getUserID(ctx), req.Model, len(req.Messages))
return err
}
```
#### B. Rate Limiting Extension
```go
type RateLimitExtension struct {
BaseExtension
limiter *rate.Limiter
}
func (e *RateLimitExtension) OnChatRequest(ctx context.Context, req *ChatRequest) error {
if !e.limiter.Allow() {
return errors.New("rate limit exceeded")
}
return nil
}
```
#### C. Cost Tracking Extension
```go
type CostTrackingExtension struct {
BaseExtension
pricing map[string]float64 // model -> price per 1K tokens
}
func (e *CostTrackingExtension) OnChatResponse(ctx context.Context, resp *ChatResponse) error {
model := resp.Model
tokens := resp.Usage.TotalTokens
cost := (float64(tokens) / 1000.0) * e.pricing[model]
log.Printf("[COST] Model: %s, Tokens: %d, Cost: $%.4f", model, tokens, cost)
// Store in user account
return e.updateUserBalance(getUserID(ctx), cost)
}
```
#### D. Content Moderation Extension
```go
type ModerationExtension struct {
BaseExtension
moderationAPI string
}
func (e *ModerationExtension) OnChatRequest(ctx context.Context, req *ChatRequest) error {
lastMsg := req.Messages[len(req.Messages)-1]
flagged, err := e.checkModeration(lastMsg.Content)
if err != nil {
return err
}
if flagged {
return errors.New("content violates policy")
}
return nil
}
```
---
### 5. **Database Schema**
#### Core Tables (`migrations/001_initial.sql`)
```sql
-- Users
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- API Configurations (multiple API keys per user)
CREATE TABLE api_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
endpoint VARCHAR(255) NOT NULL,
api_key_encrypted TEXT NOT NULL,
provider VARCHAR(50) NOT NULL, -- 'openai', 'anthropic', 'local', etc.
is_default BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW()
);
-- Chats
CREATE TABLE chats (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(500),
model VARCHAR(100),
api_config_id UUID REFERENCES api_configs(id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Messages
CREATE TABLE messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
chat_id UUID REFERENCES chats(id) ON DELETE CASCADE,
role VARCHAR(20) NOT NULL, -- 'system', 'user', 'assistant', 'function'
content TEXT NOT NULL,
name VARCHAR(100), -- for function messages
function_call JSONB, -- { "name": "...", "arguments": "..." }
metadata JSONB, -- extensible metadata
created_at TIMESTAMP DEFAULT NOW()
);
-- User Settings
CREATE TABLE user_settings (
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
settings JSONB NOT NULL DEFAULT '{}',
updated_at TIMESTAMP DEFAULT NOW()
);
-- Extension Data (for extensions to store persistent data)
CREATE TABLE extension_data (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
extension_id VARCHAR(100) NOT NULL,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
key VARCHAR(255) NOT NULL,
value JSONB NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(extension_id, user_id, key)
);
-- Request Logs (for analytics)
CREATE TABLE request_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
model VARCHAR(100),
message_count INT,
tokens_used INT,
cost DECIMAL(10, 6),
duration_ms INT,
timestamp TIMESTAMP DEFAULT NOW()
);
-- Indexes
CREATE INDEX idx_chats_user_id ON chats(user_id);
CREATE INDEX idx_messages_chat_id ON messages(chat_id);
CREATE INDEX idx_request_logs_user_id ON request_logs(user_id);
CREATE INDEX idx_request_logs_timestamp ON request_logs(timestamp);
CREATE INDEX idx_extension_data_lookup ON extension_data(extension_id, user_id, key);
```
---
### 6. **Provider Adapters**
#### OpenAI Provider (`server/providers/openai.go`)
```go
type OpenAIProvider struct {
apiKey string
endpoint string
client *http.Client
}
func (p *OpenAIProvider) ChatCompletion(ctx context.Context, req *ChatRequest) (*ChatResponse, error) {
// Standard OpenAI API implementation
}
func (p *OpenAIProvider) StreamCompletion(ctx context.Context, req *ChatRequest) (<-chan *ChatChunk, error) {
// SSE streaming implementation
}
func (p *OpenAIProvider) SupportsTools() bool {
return true
}
```
#### Anthropic Provider (`server/providers/anthropic.go`)
```go
type AnthropicProvider struct {
apiKey string
client *http.Client
}
func (p *AnthropicProvider) ChatCompletion(ctx context.Context, req *ChatRequest) (*ChatResponse, error) {
// Convert to Anthropic message format
anthropicReq := p.convertRequest(req)
// Call Anthropic API
resp := p.callAPI(anthropicReq)
// Convert back to standard format
return p.convertResponse(resp), nil
}
```
#### Local/Ollama Provider (`server/providers/ollama.go`)
```go
type OllamaProvider struct {
endpoint string // http://localhost:11434
}
func (p *OllamaProvider) ChatCompletion(ctx context.Context, req *ChatRequest) (*ChatResponse, error) {
// Call local Ollama instance
}
```
---
### 7. **API Endpoints**
#### Unified Chat Endpoint
```go
// POST /api/v1/chat/completions
// OpenAI-compatible endpoint
func (h *ChatHandler) CreateCompletion(c *gin.Context) {
var req ChatRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Execute extension hooks
if err := extensionManager.ExecuteHook("OnChatRequest", c.Request.Context(), &req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Route to appropriate provider
provider, err := modelRouter.Route(req.Model, &req)
if err != nil {
c.JSON(404, gin.H{"error": "model not found"})
return
}
// Handle streaming vs non-streaming
if req.Stream {
h.streamResponse(c, provider, &req)
} else {
resp, err := provider.ChatCompletion(c.Request.Context(), &req)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
// Execute response hooks
extensionManager.ExecuteHook("OnChatResponse", c.Request.Context(), resp)
c.JSON(200, resp)
}
}
```
#### Tool Execution Endpoint
```go
// POST /api/v1/tools/execute
func (h *ToolHandler) Execute(c *gin.Context) {
var req struct {
Tool string `json:"tool"`
Args map[string]interface{} `json:"args"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
result, err := toolRegistry.Execute(req.Tool, req.Args)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"result": result})
}
```
---
### 8. **Configuration**
#### Server Config (`server/config.yaml`)
```yaml
server:
port: 8080
cors:
allowed_origins: ["*"]
database:
host: localhost
port: 5432
name: chat_switchboard
user: postgres
password: ${DB_PASSWORD}
providers:
openai:
endpoint: https://api.openai.com/v1
api_key: ${OPENAI_API_KEY}
anthropic:
endpoint: https://api.anthropic.com/v1
api_key: ${ANTHROPIC_API_KEY}
ollama:
endpoint: http://localhost:11434
router:
default_provider: openai
fallback_chain:
- openai
- anthropic
- ollama
model_mapping:
gpt-4: openai
claude-3-opus: anthropic
llama2: ollama
extensions:
enabled:
- logging
- cost_tracking
- rate_limiting
logging:
level: info
cost_tracking:
pricing:
gpt-4: 0.03
gpt-3.5-turbo: 0.002
claude-3-opus: 0.015
rate_limiting:
requests_per_minute: 60
```
---
## 🚀 Next Steps
1. **Implement provider adapters** (`server/providers/`)
2. **Build tool registry** (`server/tools/`)
3. **Create extension manager** (`server/extensions/`)
4. **Implement database layer** (`server/models/`)
5. **Add authentication/JWT** (`server/middleware/`)
6. **Build admin panel** for extension management
7. **Create Docker setup** for easy deployment
---
**This backend architecture gives you a production-ready, infinitely extensible multi-model LLM router with function calling.**

View File

@@ -1,235 +0,0 @@
# CI/CD Infrastructure Setup - Chat Switchboard
This document outlines the CI/CD infrastructure that has been set up for the Chat Switchboard project.
## Overview
The project uses Gitea Actions for CI/CD, configured with three primary workflows:
1. **Backend CI** - Go backend testing, linting, and building
2. **Frontend CI** - JavaScript/CSS linting and standalone build
3. **Docker CI** - Container image building and publishing
## Workflows
### Backend CI (`.gitea/workflows/backend.yml`)
**Triggers:**
- Push to `server/**` or `go.mod` on `main` or `develop`
- Pull requests targeting `main` or `develop`
**Jobs:**
1. **test** - Runs Go tests with coverage
2. **lint** - Runs golangci-lint
3. **build** - Compiles Go binary (requires test & lint to pass)
**Features:**
- Auto-initializes Go module if missing
- Uploads coverage reports to Codecov
- Builds with version tags from git
- Caches Go modules for faster builds
### Frontend CI (`.gitea/workflows/frontend.yml`)
**Triggers:**
- Push to `frontend/**`, `src/**`, or `build.sh` on `main` or `develop`
- Pull requests targeting `main` or `develop`
**Jobs:**
1. **lint** - ESLint for JavaScript, Prettier for CSS
2. **build** - Generates standalone HTML via build.sh
3. **validate** - Validates HTML structure
**Features:**
- Auto-generates eslint config if missing
- Validates build output structure
- Uploads standalone build as artifact
### Docker CI (`.gitea/workflows/docker.yml`)
**Triggers:**
- Push to `main` or `develop` with Dockerfile changes
- Pull requests targeting `main` or `develop`
- Tag creation (`v*` pattern)
**Jobs:**
1. **build** - Builds and tests containers
2. **metadata** - Generates image tags
3. **push** - Pushes images to registry
4. **manifest** - Creates multi-arch manifests (main branch only)
**Features:**
- Multi-stage Docker builds
- Cache from GitHub Actions cache
- Auto-tagging based on git refs
- Security headers in nginx config
- Health checks for containers
## Docker Images
### Backend Image
**Location:** `server/Dockerfile`
**Build Process:**
1. Multi-stage build (builder → runtime)
2. Auto-generates version from git tags
3. Non-root user for security
4. Health check endpoint
**Usage:**
```bash
docker build -t chat-switchboard-backend ./server
docker run -p 8080:8080 chat-switchboard-backend
```
### Frontend Image
**Location:** `Dockerfile.frontend`
**Build Process:**
1. Builds standalone HTML in builder stage
2. Serves via nginx in production stage
3. Gzip compression enabled
4. Security headers added
**Usage:**
```bash
docker build -t chat-switchboard-frontend -f Dockerfile.frontend .
docker run -p 80:80 chat-switchboard-frontend
```
## Branch Strategy
**Branches:**
- `main` - Production branch
- `develop` - Development branch (to be created)
- Feature branches - Created from `develop`
**Workflow:**
1. Create feature branch from `develop`
2. Make changes and push
3. Create PR to `develop`
4. CI runs automatically on PR
5. Merge to `develop` after review
6. Create PR from `develop` to `main` for releases
## Getting Started
### For Developers
1. **Clone the repository**
```bash
git clone https://git.gobha.me/xcaliber/chat-switchboard.git
cd chat-switchboard
```
2. **Create feature branch**
```bash
git checkout -b feature/my-feature develop
```
3. **Make changes and test locally**
```bash
# Backend
cd server && go test ./...
# Frontend
./build.sh
```
4. **Push and create PR**
```bash
git push origin feature/my-feature
# Create PR via web interface
```
5. **CI will automatically run on your PR**
### For Maintainers
1. **Branch Protection (requires admin access)**
- Go to Repository Settings → Branches
- Add protection rule for `main` and `develop`
- Require pull request reviews
- Require status checks to pass
- Select required CI checks
2. **Creating a Release**
```bash
# Create tag
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
```
This will:
- Trigger Docker CI
- Build and push images with version tag
- Update `latest` tag on main branch
## Environment Variables
### Docker Registry
Images are pushed to GitHub Container Registry:
- Backend: `ghcr.io/xcaliber/chat-switchboard-backend`
- Frontend: `ghcr.io/xcaliber/chat-switchboard-frontend`
Authentication is handled via `GITHUB_TOKEN`.
## Monitoring & Logs
### Docker Health Checks
Both containers include health checks:
- Backend: `/health` endpoint
- Frontend: HTTP check on port 80
### CI Status
Check CI status in:
- Gitea repository → Actions tab
- Pull request checks section
## Troubleshooting
### Common Issues
**1. Go module initialization fails**
```bash
cd server
go mod init git.gobha.me/xcaliber/chat-switchboard
go mod tidy
```
**2. Frontend build fails**
```bash
chmod +x build.sh
./build.sh
```
**3. Docker build fails**
```bash
# Check if Dockerfile exists
ls -la server/Dockerfile
ls -la Dockerfile.frontend
# Build manually
docker build -t test ./server
```
### Viewing CI Logs
1. Go to Repository → Actions
2. Select the workflow run
3. View job logs for details
## Future Enhancements
Planned improvements:
- [ ] Semantic versioning automation
- [ ] Automatic changelog generation
- [ ] Integration tests in CI
- [ ] Security scanning (Trivy, Snyk)
- [ ] Performance benchmarks
- [ ] Multi-arch builds (arm64/amd64)

View File

@@ -1,469 +0,0 @@
# 🚀 Getting Started with Chat Switchboard
## What is Chat Switchboard?
Chat Switchboard is a **plugin-first, multi-model AI platform** that works in two modes:
- **🏠 Unmanaged Mode:** Runs entirely in your browser (like the current version)
- **🌐 Managed Mode:** Full backend with collaboration, workflows, and advanced features
## Quick Start (Unmanaged Mode)
### 1. Download & Run
```bash
# Clone the repo
git clone https://git.gobha.me/xcaliber/chat-switchboard.git
cd chat-switchboard
# Build standalone version
./build.sh
# Open in browser
xdg-open standalone/index.html
# or serve it
python3 -m http.server 8080 --directory standalone
```
### 2. Configure Your API
1. Click ⚙️ Settings
2. Enter your API details:
- **API Endpoint:** `https://api.openai.com/v1` (or OpenRouter, Venice.ai, etc.)
- **API Key:** Your API key
- **Model:** Select or type model name
3. Click "Save Settings"
### 3. Start Chatting
- Type a message and press Enter
- Switch models per-chat using the dropdown
- Export conversations as Markdown/JSON
**That's it!** No backend needed, all data stays in your browser.
---
## Full Installation (Managed Mode)
For teams, collaboration, and advanced features like Workflows and Channels.
### Prerequisites
- Docker & Docker Compose
- PostgreSQL 15+
- Go 1.21+ (for development)
- Python 3.9+ (for AI extensions)
### 1. Clone & Configure
```bash
git clone https://git.gobha.me/xcaliber/chat-switchboard.git
cd chat-switchboard
# Copy environment template
cp .env.example .env
# Edit configuration
nano .env
```
```bash
# .env
DATABASE_URL=postgres://user:pass@localhost:5432/chatswitch
JWT_SECRET=your-secret-key-here
PORT=8080
FRONTEND_URL=http://localhost:3000
REDIS_URL=redis://localhost:6379
```
### 2. Start Services
```bash
# Start everything with Docker Compose
docker-compose up -d
# Services:
# - PostgreSQL (5432)
# - Backend API (8080)
# - Redis (6379)
# - Frontend (3000)
```
### 3. Run Migrations
```bash
# Apply database schema
docker-compose exec backend ./migrate up
```
### 4. Create Admin User
```bash
curl -X POST http://localhost:8080/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"email": "admin@example.com",
"password": "secure-password",
"role": "admin"
}'
```
### 5. Access Frontend
Open http://localhost:3000
1. Click "Connect to Backend"
2. Enter backend URL: `http://localhost:8080`
3. Login with your credentials
---
## Architecture Overview
```
┌─────────────────────────────────────┐
│ Frontend (Vanilla JS) │
│ - Manages UI state │
│ - Switches modes (managed/local) │
└─────────────┬───────────────────────┘
┌─────────┴─────────┐
│ │
[LocalStorage] [Backend API]
│ │
│ ┌─────────▼──────────┐
│ │ Go Core │
│ │ - Auth │
│ │ - WebSocket │
│ │ - Extension Mgr │
│ └─────────┬──────────┘
│ │
│ ┌─────────┴──────────┐
│ │ PostgreSQL │
│ │ - Users, chats │
│ │ - Channels, notes │
│ │ - Vector search │
│ └────────────────────┘
└─── Extensions (Python/Go) ───┐
├─ Chat Engine │
├─ RAG Engine │
├─ Workflows │
└─ Custom Tools │
```
## Core Features
### 1. Chat (User → AI)
**Available in:** Both modes
- Multi-model support (OpenAI, Anthropic, Ollama, etc.)
- Per-conversation model switching
- Streaming responses
- Message history
- Export (Markdown, JSON, Plain Text)
**Managed mode additions:**
- Auto-routing (smart model selection)
- Cost tracking
- Shared conversations
- Server-side tool calling
### 2. Channels (User → User + AI)
**Available in:** Managed mode only
Multi-user chat rooms with AI participants:
```
#general
@alice: What do you think about this design?
@claude: I'd suggest using a darker color scheme...
@bob: Agreed! Also, @gpt4 can you review the code?
@gpt4: I see a potential issue in line 42...
```
- Public/private/DM channels
- @mention users and AI models
- Threaded conversations
- Real-time updates (WebSocket)
- Reactions and formatting
### 3. Notes & Knowledge Bases
**Available in:** Both modes (limited in unmanaged)
- Markdown notes with folders
- Tagging and search
- Linked notes (wiki-style)
**Managed mode additions:**
- Shared notes
- Knowledge base collections
- RAG (vector search)
- Semantic search with embeddings
### 4. Workflows 🌟 (UNIQUE FEATURE)
**Available in:** Managed mode only
Chain multiple AI models and tools into automated pipelines:
```
[User Query] → [Web Search] → [GPT-4 Summarize]
↓ ↓
[Save to KB] [Claude Verify]
[Generate Report]
```
**Use cases:**
- Research assistants
- Code review pipelines
- Content creation factories
- Data processing
- Multi-model consensus
See `docs/WORKFLOWS.md` for details.
## Extension System
### Frontend Extensions (Both Modes)
Simple JavaScript plugins that run in the browser:
```javascript
// extensions/frontend/my-plugin/main.js
window.ChatSwitchboard.registerExtension({
name: 'my-plugin',
hooks: {
onMessageSend: (msg) => {
console.log('Sending:', msg);
return msg; // Can modify
}
}
});
```
### Backend Extensions (Managed Only)
Full-featured plugins in any language:
```python
# extensions/backend/my-tool/main.py
from fastapi import FastAPI
app = FastAPI()
@app.post("/tools/my_tool")
async def my_tool(input: str):
result = process(input)
return {"result": result}
```
See `docs/PLUGIN_SPEC.md` for complete guide.
## Development
### Frontend Development
```bash
# Serve source files
python3 -m http.server 8080 --directory src
# Build standalone
./build.sh
```
### Backend Development
```bash
# Install Go dependencies
go mod download
# Run backend
go run server/main.go
# Or with air (hot reload)
air
```
### Extension Development
```bash
# Create from template
cp -r extensions/_template-python extensions/my-extension
# Edit files
cd extensions/my-extension
nano extension.json
nano main.py
# Test locally
python main.py
```
## Configuration
### Frontend Settings
Stored in localStorage (unmanaged) or user profile (managed):
- API endpoints and keys
- Default model
- Stream responses
- System prompt
- Temperature, max tokens, etc.
### Backend Configuration
```yaml
# config.yaml
server:
port: 8080
host: 0.0.0.0
database:
url: ${DATABASE_URL}
max_connections: 20
redis:
url: ${REDIS_URL}
enabled: true
extensions:
directory: ./extensions/backend
auto_load: true
security:
jwt_secret: ${JWT_SECRET}
jwt_expiry: 24h
rate_limit: 100/minute
```
## Switching Modes
### From Unmanaged → Managed
1. Deploy backend (see Full Installation above)
2. In Settings, enter Backend URL
3. Login/register
4. Your local data will be **uploaded** on first sync
### From Managed → Unmanaged
1. Export your data (Settings → Export)
2. Remove backend URL from settings
3. Continue using locally
**Note:** Managed-only features (Channels, Workflows) won't work in unmanaged mode.
## Common Workflows
### Personal Use (Unmanaged)
```
Open standalone/index.html → Configure API → Chat
```
### Team Collaboration (Managed)
```
Deploy backend → Create team channels → @mention AI models
```
### Development (Contributing)
```
Fork repo → Create extension → Test locally → Submit PR
```
### Self-Hosted (Privacy)
```
Deploy on your server → Configure domains → Invite team
```
## Troubleshooting
### Frontend Issues
**"No settings found"**
- Click Settings ⚙️ and configure API endpoint + key
**"API request failed"**
- Check API endpoint URL (trailing slash?)
- Verify API key is correct
- Check browser console for errors
**"LocalStorage full"**
- Export old chats
- Delete unused chats
- Clear browser data
### Backend Issues
**"Connection refused"**
- Is backend running? `docker-compose ps`
- Check firewall rules
- Verify port 8080 is open
**"Database migration failed"**
- Check PostgreSQL is running
- Verify DATABASE_URL is correct
- Run migrations manually: `./migrate up`
**"Extension not loading"**
- Check extension.json is valid
- Verify runtime is installed (python3, go, node)
- Check logs: `docker-compose logs backend`
### WebSocket Issues
**"Real-time updates not working"**
- Check WebSocket connection in browser console
- Verify Redis is running (for multi-instance setups)
- Check nginx WebSocket proxy config
## Next Steps
1. **Explore Features:** Try Chat, Notes, and (if managed) Channels
2. **Read Docs:**
- `docs/ARCHITECTURE.md` - System design
- `docs/WORKFLOWS.md` - Workflow system
- `docs/PLUGIN_SPEC.md` - Extension development
3. **Build Extensions:** Use templates in `extensions/`
4. **Join Community:** Discord, GitHub Discussions
5. **Contribute:** Submit PRs, report bugs, suggest features
## Resources
- **GitHub:** https://git.gobha.me/xcaliber/chat-switchboard
- **Documentation:** `/docs` directory
- **Examples:** `/examples` directory
- **Extensions:** `/extensions` directory
---
## FAQ
**Q: Is my data private?**
A: In unmanaged mode, everything stays in your browser. In managed mode, you control the backend.
**Q: Can I use my own models?**
A: Yes! Configure any OpenAI-compatible API (Ollama, LM Studio, etc.)
**Q: How do I migrate from ChatGPT/Claude?**
A: Export your conversations, then import via our import tool (coming soon).
**Q: Can I run this offline?**
A: Unmanaged mode works offline if you pre-load the page. For LLM calls, you need internet or local models (Ollama).
**Q: How much does it cost?**
A: Chat Switchboard is free and open-source. You pay for API usage (OpenAI, etc.) or use free models (Ollama).
**Q: Can I sell extensions?**
A: Yes! The marketplace (coming soon) will support paid extensions.
---
**Ready to switch? Start building! 🚀**

View File

@@ -1,445 +0,0 @@
# 🎨 HTML/Frontend Extensions Architecture
## Overview
This document defines the **client-side extension system** for Chat Switchboard. Extensions are HTML/JS/CSS modules that can hook into the application lifecycle, add UI elements, intercept messages, and extend functionality.
---
## 🔌 Extension Interface
### Extension Manifest Structure
```javascript
{
"id": "example-extension",
"name": "Example Extension",
"version": "1.0.0",
"author": "Your Name",
"description": "Does something cool",
"type": "frontend", // or "hybrid" if it has backend components
"permissions": ["ui", "messages", "storage"],
"entrypoint": "extension.js",
"styles": "extension.css",
"hooks": {
"onLoad": true,
"onMessageSend": true,
"onMessageReceive": true,
"onModelSwitch": true
},
"ui": {
"toolbar": true,
"sidebar": false,
"settings": true
}
}
```
---
## 📦 Extension API
### Core Extension Class
```javascript
class ChatSwitchboardExtension {
constructor(manifest) {
this.manifest = manifest;
this.enabled = true;
}
// Lifecycle hooks
onLoad() {}
onUnload() {}
onEnable() {}
onDisable() {}
// Message hooks
async onMessageSend(message, context) {
// Modify message before sending
// return modified message or null to cancel
return message;
}
async onMessageReceive(message, context) {
// Process received message
// return modified message or original
return message;
}
async onMessageDisplay(message, element) {
// Modify message DOM before display
return element;
}
// UI hooks
onModelSwitch(oldModel, newModel) {}
onChatSwitch(oldChatId, newChatId) {}
onSettingsOpen() {}
// Render hooks
renderToolbarButton() {
// Return HTML string or DOM element
return null;
}
renderSidebarPanel() {
return null;
}
renderSettingsPanel() {
return null;
}
renderMessageAction(message) {
// Add custom actions to message bubbles
return null;
}
}
```
---
## 🎯 Extension Categories
### 1. **UI Extensions**
Add visual elements and interface enhancements.
**Examples:**
- Syntax highlighter themes
- Message templates/snippets
- Custom emoji pickers
- Voice input buttons
- Image/file attachments
**Hooks:** `renderToolbarButton`, `renderSidebarPanel`, `renderMessageAction`
---
### 2. **Message Processors**
Transform messages before/after sending.
**Examples:**
- Markdown preprocessor
- Code formatter
- Translation layer
- Prompt templates
- Token counter
**Hooks:** `onMessageSend`, `onMessageReceive`, `onMessageDisplay`
---
### 3. **Model Extensions**
Enhance model selection and routing.
**Examples:**
- Model performance stats
- Cost calculator
- Context window manager
- Model recommendation engine
- Fallback routing (if model fails, try another)
**Hooks:** `onModelSwitch`, access to `State.settings.model`
---
### 4. **Storage Extensions**
Extend data persistence capabilities.
**Examples:**
- Cloud sync (S3, Dropbox)
- Export formats (PDF, DOCX)
- Search indexing
- Tagging system
- Chat organization
**Permissions:** `storage`
---
### 5. **Tool/Function Extensions**
Enable LLM function calling (critical for your use case).
**Examples:**
- Web search tool
- Calculator
- Code execution sandbox
- API caller
- File system access
**Hooks:** `onFunctionCall`, `registerFunction`
---
## 🛠️ Extension Manager
### Core API
```javascript
// Global extension registry
window.ExtensionManager = {
extensions: new Map(),
// Register an extension
register(manifest, ExtensionClass) {
const ext = new ExtensionClass(manifest);
this.extensions.set(manifest.id, ext);
ext.onLoad();
return ext;
},
// Enable/disable
enable(id) {},
disable(id) {},
unload(id) {},
// Hook execution
async executeHook(hookName, ...args) {
const results = [];
for (const [id, ext] of this.extensions) {
if (ext.enabled && ext[hookName]) {
const result = await ext[hookName](...args);
results.push({ id, result });
}
}
return results;
},
// Get all extensions with specific capability
getByPermission(permission) {
return Array.from(this.extensions.values())
.filter(ext => ext.manifest.permissions.includes(permission));
}
};
```
---
## 📂 Extension File Structure
```
extensions/
├── example-extension/
│ ├── manifest.json
│ ├── extension.js
│ ├── extension.css (optional)
│ ├── README.md
│ └── assets/
│ └── icon.svg
```
### Loading Extensions
```javascript
async function loadExtension(path) {
const manifest = await fetch(`${path}/manifest.json`).then(r => r.json());
// Load JS
const module = await import(`${path}/${manifest.entrypoint}`);
// Load CSS if present
if (manifest.styles) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = `${path}/${manifest.styles}`;
document.head.appendChild(link);
}
// Register
ExtensionManager.register(manifest, module.default);
}
```
---
## 🔐 Security & Permissions
### Permission System
```javascript
const PERMISSIONS = {
ui: "Modify user interface",
messages: "Read and modify messages",
storage: "Access local storage",
network: "Make network requests",
settings: "Access user settings",
clipboard: "Access clipboard",
notifications: "Show notifications"
};
```
### Sandboxing (Future)
- Use iframes for untrusted extensions
- Content Security Policy restrictions
- API key scoping (extensions can't access main API key)
---
## 🎨 Example Extensions
### 1. Token Counter
```javascript
class TokenCounterExtension extends ChatSwitchboardExtension {
onLoad() {
this.tokenCount = 0;
}
renderToolbarButton() {
return `
<button class="btn btn-small" id="tokenCounter">
Tokens: <span id="tokenCount">0</span>
</button>
`;
}
async onMessageSend(message, context) {
// Rough estimation: 1 token ≈ 4 chars
this.tokenCount = Math.ceil(message.content.length / 4);
document.getElementById('tokenCount').textContent = this.tokenCount;
return message;
}
}
```
### 2. Code Formatter
```javascript
class CodeFormatterExtension extends ChatSwitchboardExtension {
async onMessageDisplay(message, element) {
if (message.role === 'assistant') {
// Find code blocks and apply Prism.js highlighting
element.querySelectorAll('pre code').forEach(block => {
Prism.highlightElement(block);
});
}
return element;
}
renderMessageAction(message) {
if (message.content.includes('```')) {
return `
<button class="btn btn-small" onclick="copyAllCode()">
📋 Copy All Code
</button>
`;
}
return null;
}
}
```
### 3. Model Router (Smart Fallback)
```javascript
class ModelRouterExtension extends ChatSwitchboardExtension {
async onMessageSend(message, context) {
// If message is long, use high-context model
if (message.content.length > 5000) {
const originalModel = State.settings.model;
State.settings.model = 'claude-3-opus-20240229'; // High context
// Restore after response
context.onComplete = () => {
State.settings.model = originalModel;
};
}
return message;
}
}
```
---
## 🚀 Integration with App
### Modify `app.js` to support hooks:
```javascript
async function sendMessage() {
// ... existing code ...
// Execute hook before sending
const hookResults = await ExtensionManager.executeHook(
'onMessageSend',
message,
{ chatId: State.currentChatId }
);
// Check if any extension cancelled the send
if (hookResults.some(r => r.result === null)) {
return;
}
// Apply transformations
for (const { result } of hookResults) {
if (result && result !== message) {
message = result;
}
}
// ... continue with API call ...
}
```
---
## 📋 Extension Discovery
### Extension Store (Future)
```javascript
{
"extensions": [
{
"id": "web-search",
"name": "Web Search Tool",
"description": "Adds web search capability via function calling",
"author": "Chat Switchboard Team",
"version": "1.0.0",
"downloadUrl": "https://extensions.switchboard/web-search.zip",
"verified": true,
"rating": 4.8,
"downloads": 1250
}
]
}
```
---
## 🎯 Next Steps
1. **Implement ExtensionManager** in `src/js/extensions.js`
2. **Add hook points** to existing code (app.js, ui.js, api.js)
3. **Create example extensions** to validate API
4. **Build extension settings UI** for enable/disable/configure
5. **Document extension development** with starter template
---
## 🤝 Extension Development Workflow
```bash
# Create new extension
mkdir extensions/my-extension
cd extensions/my-extension
# Generate manifest
cat > manifest.json << EOF
{
"id": "my-extension",
"name": "My Extension",
"version": "1.0.0",
"entrypoint": "extension.js",
"permissions": ["messages"]
}
EOF
# Create extension class
cat > extension.js << EOF
class MyExtension extends ChatSwitchboardExtension {
onLoad() {
console.log('My extension loaded!');
}
}
export default MyExtension;
EOF
# Test in dev mode
# Extensions auto-load from /extensions/ directory
```
---
**This architecture makes Chat Switchboard infinitely extensible while keeping the core lean.**

View File

@@ -1,759 +0,0 @@
# 🔌 Plugin Specification - Extension Development Guide
## Philosophy: Everything is a Plugin
**Core Principle:** If core features (Chat, Channels, Notes) are implemented as plugins, we prove that ANY feature can be a plugin.
```
Minimal Core (Go) Extensions (Any Language)
├── HTTP Router ├── Chat Engine (Python)
├── WebSocket Hub ├── Channels (Go)
├── Extension Manager ├── RAG Engine (Python)
├── Auth/Users ├── Workflows (Go + Python)
└── PostgreSQL └── Your Custom Plugin...
```
## Plugin Types
### 1. Frontend Plugins (UI Only)
**Language:** JavaScript
**Runs:** In browser
**Capabilities:** UI modifications, client-side logic
**Modes:** Both managed and unmanaged
### 2. Backend Plugins (Full Power)
**Language:** Python, Go, Node.js, Rust, etc.
**Runs:** As separate process
**Capabilities:** Tools, models, data processing, external APIs
**Modes:** Managed only
## Frontend Plugin API
### Plugin Structure
```
extensions/frontend/token-counter/
├── manifest.json # Plugin metadata
├── main.js # Entry point
├── styles.css # Optional styles
└── README.md
```
### manifest.json
```json
{
"name": "token-counter",
"version": "1.0.0",
"author": "Your Name",
"description": "Display token count for messages",
"type": "frontend",
"entry": "main.js",
"permissions": [
"message:read",
"ui:inject"
],
"hooks": [
"onMessageSend",
"onMessageReceive",
"onUIRender"
]
}
```
### main.js Example
```javascript
// Frontend plugin template
(function() {
'use strict';
// Plugin initialization
window.ChatSwitchboard = window.ChatSwitchboard || {};
window.ChatSwitchboard.plugins = window.ChatSwitchboard.plugins || [];
const TokenCounter = {
name: 'token-counter',
version: '1.0.0',
// Called when plugin loads
init: function() {
console.log('Token Counter plugin loaded');
this.addUI();
},
// Hook: Before message is sent
hooks: {
onMessageSend: function(message) {
const tokens = this.estimateTokens(message.content);
console.log(`Message has ~${tokens} tokens`);
// Show warning if too long
if (tokens > 4000) {
window.ChatSwitchboard.showToast(
`⚠️ Long message: ${tokens} tokens`,
'warning'
);
}
// Can modify message before sending
return message;
},
onMessageReceive: function(message) {
// Process incoming messages
return message;
},
onUIRender: function() {
// Inject UI elements
this.updateTokenDisplay();
}
},
// Add token counter to UI
addUI: function() {
const inputArea = document.querySelector('.input-area');
const counter = document.createElement('div');
counter.id = 'token-counter';
counter.className = 'plugin-token-counter';
counter.textContent = '0 tokens';
inputArea.appendChild(counter);
// Update on input
const textarea = document.getElementById('messageInput');
textarea.addEventListener('input', () => {
const tokens = this.estimateTokens(textarea.value);
counter.textContent = `${tokens} tokens`;
});
},
// Token estimation (rough approximation)
estimateTokens: function(text) {
return Math.ceil(text.length / 4);
},
updateTokenDisplay: function() {
const counter = document.getElementById('token-counter');
const input = document.getElementById('messageInput');
if (counter && input) {
const tokens = this.estimateTokens(input.value);
counter.textContent = `${tokens} tokens`;
}
}
};
// Register plugin
window.ChatSwitchboard.plugins.push(TokenCounter);
// Auto-init when DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => TokenCounter.init());
} else {
TokenCounter.init();
}
})();
```
### Available Hooks
```javascript
// Message lifecycle
onMessageSend(message) // Before sending to API
onMessageReceive(message) // After receiving from API
onMessageRender(element, message) // When rendering to DOM
// UI lifecycle
onUIRender() // After UI updates
onChatSwitch(chatId) // When switching chats
onModelChange(model) // When model changes
// Extension points
onToolCall(tool, params) // Before calling tool
onToolResult(tool, result) // After tool returns
// Settings
onSettingsOpen() // Settings modal opened
onSettingsSave(settings) // Settings saved
// Files
onFileUpload(file) // File uploaded
onFileSelect(file) // File selected for chat
```
### Plugin API Methods
```javascript
// Toast notifications
ChatSwitchboard.showToast(message, type); // type: success, error, warning, info
// Storage (scoped to plugin)
ChatSwitchboard.storage.set(key, value);
ChatSwitchboard.storage.get(key);
ChatSwitchboard.storage.remove(key);
// UI manipulation
ChatSwitchboard.ui.addButton(location, config);
ChatSwitchboard.ui.addPanel(config);
ChatSwitchboard.ui.addMenuItem(config);
// State access (read-only)
ChatSwitchboard.state.getCurrentChat();
ChatSwitchboard.state.getCurrentModel();
ChatSwitchboard.state.getSettings();
// Events
ChatSwitchboard.events.on(event, callback);
ChatSwitchboard.events.off(event, callback);
ChatSwitchboard.events.emit(event, data);
```
## Backend Plugin API
### Plugin Structure
```
extensions/backend/web-search/
├── extension.json # Manifest
├── main.py # Entry point (or main.go, server.js, etc)
├── requirements.txt # Dependencies (Python)
├── config.yaml # Configuration
└── README.md
```
### extension.json
```json
{
"name": "web-search",
"version": "1.0.0",
"author": "Chat Switchboard Team",
"description": "Search the web using DuckDuckGo API",
"type": "backend",
"runtime": "python",
"entry": "main.py",
"port": 9001,
"capabilities": ["tool"],
"dependencies": {
"python": ">=3.9",
"packages": ["fastapi", "uvicorn", "duckduckgo-search"]
},
"tools": [
{
"name": "search_web",
"description": "Search the web for information",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query"
},
"max_results": {
"type": "integer",
"description": "Maximum results to return",
"default": 5
}
},
"required": ["query"]
},
"returns": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"url": {"type": "string"},
"snippet": {"type": "string"}
}
}
}
}
}
}
],
"config": {
"max_requests_per_minute": 30,
"timeout_seconds": 10
}
}
```
### Python Plugin Template
```python
# extensions/backend/web-search/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from duckduckgo_search import DDGS
import uvicorn
import os
app = FastAPI(title="Web Search Plugin")
# Load config
PORT = int(os.getenv("PLUGIN_PORT", 9001))
MAX_RESULTS = int(os.getenv("MAX_RESULTS", 5))
# Request/response models
class SearchRequest(BaseModel):
query: str
max_results: int = 5
class SearchResult(BaseModel):
title: str
url: str
snippet: str
class SearchResponse(BaseModel):
results: list[SearchResult]
# Tool endpoint
@app.post("/tools/search_web", response_model=SearchResponse)
async def search_web(request: SearchRequest):
"""Search the web using DuckDuckGo"""
try:
ddgs = DDGS()
results = ddgs.text(
request.query,
max_results=min(request.max_results, MAX_RESULTS)
)
return SearchResponse(
results=[
SearchResult(
title=r.get("title", ""),
url=r.get("href", ""),
snippet=r.get("body", "")
)
for r in results
]
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# Health check
@app.get("/health")
async def health():
return {"status": "ok", "plugin": "web-search"}
# Plugin info
@app.get("/info")
async def info():
return {
"name": "web-search",
"version": "1.0.0",
"tools": ["search_web"]
}
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=PORT)
```
### Go Plugin Template
```go
// extensions/backend/file-ops/main.go
package main
import (
"github.com/gin-gonic/gin"
"os"
"io/ioutil"
)
type ReadFileRequest struct {
Path string `json:"path" binding:"required"`
}
type ReadFileResponse struct {
Content string `json:"content"`
Size int64 `json:"size"`
}
func main() {
r := gin.Default()
// Tool endpoint
r.POST("/tools/read_file", func(c *gin.Context) {
var req ReadFileRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
content, err := ioutil.ReadFile(req.Path)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
info, _ := os.Stat(req.Path)
c.JSON(200, ReadFileResponse{
Content: string(content),
Size: info.Size(),
})
})
// Health check
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})
// Start server
port := os.Getenv("PLUGIN_PORT")
if port == "" {
port = "9002"
}
r.Run(":" + port)
}
```
## Extension Manager (Backend Core)
### Go Extension Manager
```go
// server/extensions/manager.go
package extensions
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sync"
)
type Extension struct {
Name string `json:"name"`
Version string `json:"version"`
Runtime string `json:"runtime"`
Entry string `json:"entry"`
Port int `json:"port"`
Tools []Tool `json:"tools"`
Process *os.Process
BaseURL string
}
type Tool struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters map[string]interface{} `json:"parameters"`
}
type Manager struct {
extensions map[string]*Extension
tools map[string]*Tool
mu sync.RWMutex
}
func NewManager() *Manager {
return &Manager{
extensions: make(map[string]*Extension),
tools: make(map[string]*Tool),
}
}
// Load all extensions from directory
func (m *Manager) LoadExtensions(dir string) error {
entries, err := ioutil.ReadDir(dir)
if err != nil {
return err
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
extPath := filepath.Join(dir, entry.Name())
if err := m.LoadExtension(extPath); err != nil {
log.Printf("Failed to load extension %s: %v", entry.Name(), err)
}
}
return nil
}
// Load single extension
func (m *Manager) LoadExtension(path string) error {
// Read manifest
manifestPath := filepath.Join(path, "extension.json")
data, err := ioutil.ReadFile(manifestPath)
if err != nil {
return err
}
var ext Extension
if err := json.Unmarshal(data, &ext); err != nil {
return err
}
// Start extension process
if err := m.startExtension(&ext, path); err != nil {
return err
}
// Register tools
for i := range ext.Tools {
ext.Tools[i].ExtensionName = ext.Name
m.tools[ext.Tools[i].Name] = &ext.Tools[i]
}
m.mu.Lock()
m.extensions[ext.Name] = &ext
m.mu.Unlock()
log.Printf("✅ Loaded extension: %s v%s", ext.Name, ext.Version)
return nil
}
// Start extension process
func (m *Manager) startExtension(ext *Extension, path string) error {
var cmd *exec.Cmd
switch ext.Runtime {
case "python":
cmd = exec.Command("python3", ext.Entry)
case "go":
cmd = exec.Command("go", "run", ext.Entry)
case "node":
cmd = exec.Command("node", ext.Entry)
default:
return fmt.Errorf("unsupported runtime: %s", ext.Runtime)
}
cmd.Dir = path
cmd.Env = append(os.Environ(), fmt.Sprintf("PLUGIN_PORT=%d", ext.Port))
if err := cmd.Start(); err != nil {
return err
}
ext.Process = cmd.Process
ext.BaseURL = fmt.Sprintf("http://localhost:%d", ext.Port)
// Wait for extension to be ready
time.Sleep(2 * time.Second)
return nil
}
// Call tool
func (m *Manager) CallTool(name string, params map[string]interface{}) (interface{}, error) {
m.mu.RLock()
tool, exists := m.tools[name]
m.mu.RUnlock()
if !exists {
return nil, fmt.Errorf("tool not found: %s", name)
}
ext := m.extensions[tool.ExtensionName]
url := fmt.Sprintf("%s/tools/%s", ext.BaseURL, name)
// HTTP POST to extension
body, _ := json.Marshal(params)
resp, err := http.Post(url, "application/json", bytes.NewBuffer(body))
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result interface{}
json.NewDecoder(resp.Body).Decode(&result)
return result, nil
}
// List all tools
func (m *Manager) ListTools() []Tool {
m.mu.RLock()
defer m.mu.RUnlock()
tools := make([]Tool, 0, len(m.tools))
for _, tool := range m.tools {
tools = append(tools, *tool)
}
return tools
}
// Shutdown all extensions
func (m *Manager) Shutdown() {
m.mu.Lock()
defer m.mu.Unlock()
for _, ext := range m.extensions {
if ext.Process != nil {
ext.Process.Kill()
}
}
}
```
### Extension Discovery
```go
// server/main.go
func main() {
// ... existing setup ...
// Load extensions
extManager := extensions.NewManager()
if err := extManager.LoadExtensions("./extensions/backend"); err != nil {
log.Fatal("Failed to load extensions:", err)
}
defer extManager.Shutdown()
// Expose extension tools via API
r.GET("/api/tools", func(c *gin.Context) {
tools := extManager.ListTools()
c.JSON(200, tools)
})
r.POST("/api/tools/:name", func(c *gin.Context) {
toolName := c.Param("name")
var params map[string]interface{}
c.BindJSON(&params)
result, err := extManager.CallTool(toolName, params)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, result)
})
}
```
## Plugin Development Workflow
### 1. Create from Template
```bash
# Frontend plugin
cp -r extensions/frontend/_template extensions/frontend/my-plugin
cd extensions/frontend/my-plugin
# Edit manifest.json, main.js
# Backend plugin (Python)
cp -r extensions/backend/_template-python extensions/backend/my-plugin
cd extensions/backend/my-plugin
# Edit extension.json, main.py
pip install -r requirements.txt
```
### 2. Test Locally
```bash
# Backend plugin
python main.py # Runs on port from extension.json
# Test tool
curl -X POST http://localhost:9001/tools/my_tool \
-H "Content-Type: application/json" \
-d '{"param": "value"}'
```
### 3. Install in Chat Switchboard
```bash
# Move to extensions directory
mv my-plugin ../chat-switchboard/extensions/backend/
# Restart backend
cd ../chat-switchboard
docker-compose restart backend
```
### 4. Publish to Marketplace
```bash
# Package extension
tar -czf my-plugin-1.0.0.tar.gz my-plugin/
# Upload to marketplace
curl -X POST https://marketplace.chatswitch.io/api/publish \
-F "file=@my-plugin-1.0.0.tar.gz" \
-F "category=tools" \
-H "Authorization: Bearer $TOKEN"
```
## Security & Sandboxing
### Backend Plugin Isolation
```yaml
# docker-compose.yml - Run plugins in containers
services:
plugin-web-search:
build: ./extensions/backend/web-search
ports:
- "9001:9001"
environment:
- PLUGIN_PORT=9001
networks:
- extension-network
restart: always
```
### Permission System
```json
// Plugins declare required permissions
{
"permissions": [
"network:outbound", // Make external HTTP requests
"storage:read", // Read from DB
"storage:write", // Write to DB
"file:read", // Read files
"tool:call:*" // Call other tools
]
}
```
## Official Plugin List
### Bundled with Core
1. **chat-engine** (Python) - LLM conversation handling
2. **channels** (Go) - Multi-user chat rooms
3. **rag-engine** (Python) - Vector search, embeddings
4. **workflows** (Go/Python) - Workflow orchestration
5. **notes** (Go) - Note management
### Official Extensions
6. **web-search** (Python) - DuckDuckGo search
7. **code-runner** (Python) - Sandboxed code execution
8. **image-gen** (Python) - DALL-E/Stable Diffusion
9. **file-ops** (Go) - File system operations
10. **calculator** (Python) - Math evaluation
### Community Extensions (Example Ideas)
- **slack-integration** - Post to Slack channels
- **github-integration** - Create issues, PRs
- **email-sender** - Send emails via SMTP
- **pdf-parser** - Extract text from PDFs
- **scraper** - Web scraping tool
- **translate** - Multi-language translation
- **tts** - Text-to-speech
- **stt** - Speech-to-text
---
## Next Steps
1. Read `docs/ARCHITECTURE.md` for system overview
2. Check `extensions/_template-python/` for starter code
3. Browse `extensions/backend/` for examples
4. Join developer Discord for help
**Build something awesome! 🚀**

View File

@@ -1,360 +0,0 @@
# 🚀 Quick Start for Developers
Get up and running with Chat Switchboard development in under 10 minutes.
## Prerequisites
- **Go 1.21+** - Backend
- **Python 3.11+** - Extensions
- **PostgreSQL 15+** - Database
- **Redis 7+** - WebSocket scaling
- **Node.js 18+** (optional) - For frontend tooling
- **Docker** (optional) - For containerized development
## 🏃 Quick Setup
### 1. Clone and Enter
```bash
git clone https://git.gobha.me/xcaliber/chat-switchboard.git
cd chat-switchboard
```
### 2. Start Services (Docker)
```bash
docker-compose up -d
```
This starts:
- PostgreSQL on `localhost:5432`
- Redis on `localhost:6379`
### 3. Backend Setup
```bash
cd server
go mod download
cp .env.example .env
# Edit .env with database credentials
# Run migrations
go run cmd/migrate/main.go up
# Start server
go run main.go
```
Backend runs on `http://localhost:8080`
### 4. Frontend Setup
```bash
cd src
python3 -m http.server 3000
```
Frontend runs on `http://localhost:3000`
### 5. Open Browser
```
http://localhost:3000
```
Configure API settings to point to `http://localhost:8080`
---
## 🎯 Your First Contribution
### Step 1: Pick an Issue
Browse [issues](https://git.gobha.me/xcaliber/chat-switchboard/issues) and pick one labeled:
- 🟡 **LOW** priority
- `good-first-issue`
### Step 2: Create Branch
```bash
git checkout develop
git pull origin develop
git checkout -b issue-XX-description
```
### Step 3: Make Changes
Edit files, test locally:
```bash
# Backend tests
cd server
go test ./...
# Frontend lint (if ESLint configured)
cd src
npm run lint
```
### Step 4: Commit and Push
```bash
git add .
git commit -m "[Category] Description (refs #XX)"
git push origin issue-XX-description
```
### Step 5: Open PR
Go to GitHub/Gitea and open PR:
- **Base:** `develop`
- **Title:** `[Category] Description (closes #XX)`
- Fill in PR template
### Step 6: Wait for Review
Maintainers will review and provide feedback.
---
## 🔧 Development Commands
### Backend
```bash
# Run server
go run main.go
# Run tests
go test ./...
# Run tests with coverage
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# Lint
golangci-lint run
# Build
go build -o bin/server main.go
# Run migrations
go run cmd/migrate/main.go up
go run cmd/migrate/main.go down
```
### Frontend
```bash
# Serve for development
python3 -m http.server 3000
# Build standalone
./build.sh
# Lint (if configured)
npm run lint
```
### Docker
```bash
# Start all services
docker-compose up -d
# View logs
docker-compose logs -f
# Restart a service
docker-compose restart backend
# Stop all
docker-compose down
```
---
## 📁 Project Structure Quick Reference
```
chat-switchboard/
├── server/ # Go backend
│ ├── main.go # Entry point
│ ├── handlers/ # API handlers
│ ├── middleware/ # Auth, CORS, etc.
│ ├── models/ # Database models
│ ├── extensions/ # Extension manager
│ ├── websocket/ # WebSocket hub
│ └── workflows/ # Workflow engine
├── src/ # Frontend
│ ├── index.html
│ ├── js/
│ │ ├── app.js # Main app logic
│ │ ├── state.js # State management
│ │ ├── api.js # API calls
│ │ └── ui.js # UI rendering
│ └── css/
├── extensions/ # Backend extensions
│ ├── _template-python/
│ ├── chat-engine/
│ ├── web-search/
│ └── calculator/
├── migrations/ # Database migrations
├── docs/ # Documentation
│ ├── ARCHITECTURE.md
│ ├── WORKFLOWS.md
│ ├── PLUGIN_SPEC.md
│ └── GETTING_STARTED.md
├── ROADMAP.md # Development roadmap
└── ISSUES.md # Issue workflow
```
---
## 🐍 Creating Your First Extension
### 1. Copy Template
```bash
cp -r extensions/_template-python extensions/my-extension
cd extensions/my-extension
```
### 2. Edit Manifest
```json
{
"name": "my-extension",
"version": "1.0.0",
"runtime": "python",
"entry": "main.py",
"port": 9001,
"tools": [
{
"name": "my_tool",
"description": "Does something cool",
"parameters": {
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "Input text"
}
}
}
}
]
}
```
### 3. Implement Tool
```python
# main.py
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.post("/tools/my_tool")
async def my_tool(input: str):
# Your logic here
result = input.upper() # Example
return {"result": result}
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=9001)
```
### 4. Test
```bash
python main.py
# In another terminal:
curl -X POST http://localhost:9001/tools/my_tool \
-H "Content-Type: application/json" \
-d '{"input": "hello"}'
```
### 5. Register with Backend
Backend auto-discovers extensions in `/extensions` folder on startup.
---
## 🧪 Running Tests
### Backend Unit Tests
```bash
cd server
go test ./handlers -v
go test ./extensions -v
go test ./workflows -v
```
### Integration Tests
```bash
# Start services
docker-compose up -d
# Run tests
go test ./tests/integration -v
```
### E2E Tests
```bash
# Install Playwright (one-time)
npm install -D @playwright/test
npx playwright install
# Run E2E tests
npx playwright test
```
---
## 🔍 Debugging
### Backend Debugging
```bash
# Run with delve debugger
dlv debug main.go
# Or use VS Code launch.json
# F5 to start debugging
```
### Frontend Debugging
- Open browser DevTools (F12)
- Check Console for errors
- Use Network tab for API calls
### Extension Debugging
```bash
# Run extension standalone
cd extensions/my-extension
python main.py
# Check logs
tail -f logs/extension.log
```
---
## 📚 Essential Reading
Before you start coding:
1. **[ARCHITECTURE.md](ARCHITECTURE.md)** - System design
2. **[PLUGIN_SPEC.md](PLUGIN_SPEC.md)** - Extension development
3. **[ISSUES.md](../ISSUES.md)** - Contribution workflow
4. **[ROADMAP.md](../ROADMAP.md)** - Project direction
---
## 💬 Getting Help
- **Issues:** [GitHub Issues](https://git.gobha.me/xcaliber/chat-switchboard/issues)
- **Discussions:** (Coming soon)
- **Discord:** (Coming soon)
---
## ✨ Pro Tips
1. **Use `develop` branch** - Never commit directly to `main`
2. **Small PRs** - Easier to review, faster to merge
3. **Test locally** - Don't rely on CI to catch errors
4. **Ask questions** - Better to ask than assume
5. **Read existing code** - Learn patterns from implemented features
---
## 🎉 You're Ready!
Pick an issue and start coding. Welcome to the team! 🚀
**Next Steps:**
1. Browse [open issues](https://git.gobha.me/xcaliber/chat-switchboard/issues)
2. Read the [ROADMAP](../ROADMAP.md)
3. Make your first PR!

View File

@@ -1,575 +0,0 @@
# 🔄 Workflow System - The Killer Feature
## What Are Workflows?
Workflows let you **chain multiple AI models, tools, and data sources** into automated pipelines. Think Zapier/n8n but for LLMs.
### Why This Matters
**Current State (Competitors):**
```
User → Single Prompt → One Model → Response
```
**Chat Switchboard Workflows:**
```
User Input → [Model A] → [Tool] → [Model B] → [Conditional Logic] → Final Output
↓ logs ↓ saves ↓ verifies ↓ branches
```
## Real-World Examples
### 1. Research Assistant
**Problem:** Researching a topic requires multiple steps and models
```
┌─────────────┐
│ User Query │
└──────┬──────┘
┌─────────────────────┐
│ 1. Web Search Tool │ ← DuckDuckGo API
└──────┬──────────────┘
│ (top 5 results)
┌─────────────────────────┐
│ 2. GPT-4: Extract Facts │ ← Fast, cheap summarization
└──────┬──────────────────┘
│ (structured data)
┌──────────────────────────┐
│ 3. Claude: Verify Claims │ ← Accurate fact-checking
└──────┬───────────────────┘
│ (verified facts)
┌──────────────────────────┐
│ 4. Gemini: Write Report │ ← Long-form generation
└──────┬───────────────────┘
│ (final report)
┌──────────────────────┐
│ 5. Save to KB Tool │ ← Store for future reference
└──────────────────────┘
```
**Time Saved:** 30 minutes → 2 minutes
**Cost Optimization:** Uses cheap models where possible
### 2. Code Review Pipeline
```
[GitHub PR] → [Fetch Code Tool] → [GPT-4: Find Bugs] → [Claude: Security Check]
↓ ↓
[Save Issues] [Create Report]
[Post to Channel Tool]
```
### 3. Multi-Model Consensus
**Problem:** Get the "best" answer by combining multiple AI perspectives
```
┌─[GPT-4]─┐
│ │
[User Question] ────┼─[Claude]─┼──→ [Voting Logic] ──→ [Best Answer]
│ │
└─[Gemini]┘
```
**Use Case:** Medical advice, legal analysis, important decisions
### 4. Content Creation Factory
```
[Topic] → [GPT-4: Outline] → [Claude: Draft] → [Grammar Tool] → [SEO Tool] → [Publish]
↓ ↓ ↓ ↓
[Save Draft] [Version 1] [Version 2] [Final]
```
### 5. Data Processing Pipeline
```
[CSV Upload] → [Python Tool: Parse] → [GPT-3.5: Categorize] → [Save to PostgreSQL]
[Generate Report Tool]
```
## Workflow Builder UI
### Visual Editor (Drag & Drop)
```
+------------------+ +------------------+ +------------------+
| [Web Search] |---->| [GPT-4 Node] |---->| [Save Note] |
| | | | | |
| Query: {input} | | Prompt: Summary | | Title: Research |
+------------------+ +------------------+ +------------------+
↓ ↓ ↓
[5 results] [Markdown text] [Note ID]
```
### Node Types
#### 1. Input Nodes
- **User Input** - Text, file, form
- **Trigger** - Schedule, webhook, event
- **Variable** - Constants, config
#### 2. Model Nodes
- **LLM Call** - Any configured model
- **Model Router** - Auto-select best model
- **Multi-Model** - Run parallel, compare
#### 3. Tool Nodes
- **Web Search**
- **Code Execution**
- **API Call**
- **Database Query**
- **File Operations**
- **Custom Extension Tools**
#### 4. Logic Nodes
- **Conditional** - If/else branching
- **Loop** - Iterate over data
- **Merge** - Combine results
- **Filter** - Data transformation
#### 5. Output Nodes
- **Response** - Return to user
- **Save** - Store in DB/KB
- **Webhook** - External notification
- **Channel Message** - Post to channel
## Workflow Definition (JSON)
```json
{
"id": "research-assistant",
"name": "Research Assistant",
"version": "1.0.0",
"description": "Search, summarize, verify, and save research",
"nodes": [
{
"id": "input",
"type": "input",
"config": {
"schema": {
"query": {"type": "string", "required": true}
}
}
},
{
"id": "search",
"type": "tool",
"tool": "web_search",
"config": {
"query": "{{input.query}}",
"max_results": 5
}
},
{
"id": "summarize",
"type": "llm",
"model": "gpt-4o-mini",
"config": {
"prompt": "Summarize these search results:\n{{search.results}}",
"max_tokens": 500
}
},
{
"id": "verify",
"type": "llm",
"model": "claude-3.5-sonnet",
"config": {
"prompt": "Verify the accuracy of:\n{{summarize.content}}",
"max_tokens": 300
}
},
{
"id": "save",
"type": "tool",
"tool": "save_to_kb",
"config": {
"kb_id": "research",
"title": "{{input.query}}",
"content": "{{verify.content}}"
}
},
{
"id": "output",
"type": "output",
"config": {
"response": "{{verify.content}}"
}
}
],
"edges": [
{"from": "input", "to": "search"},
{"from": "search", "to": "summarize"},
{"from": "summarize", "to": "verify"},
{"from": "verify", "to": "save"},
{"from": "save", "to": "output"}
]
}
```
## Execution Engine
### Go Workflow Runner
```go
// server/workflows/executor.go
package workflows
type Executor struct {
workflow *Workflow
context map[string]interface{}
llmClient *LLMClient
toolRegistry *ToolRegistry
}
func (e *Executor) Run(input map[string]interface{}) (*Result, error) {
e.context["input"] = input
// Topological sort to execute DAG in order
sorted := e.workflow.TopologicalSort()
for _, node := range sorted {
result, err := e.executeNode(node)
if err != nil {
return nil, err
}
e.context[node.ID] = result
}
return e.context["output"], nil
}
func (e *Executor) executeNode(node *Node) (interface{}, error) {
switch node.Type {
case "llm":
return e.executeLLM(node)
case "tool":
return e.executeTool(node)
case "conditional":
return e.executeConditional(node)
// ... other types
}
}
func (e *Executor) executeLLM(node *Node) (interface{}, error) {
// Resolve template variables
prompt := e.resolveTemplate(node.Config["prompt"])
model := node.Config["model"]
// Call LLM
response, err := e.llmClient.Chat(model, prompt)
return response, err
}
```
### Variable Resolution
```go
// Template: "Summarize: {{search.results}}"
// Context: {"search": {"results": "..."}}
// Result: "Summarize: ..."
func (e *Executor) resolveTemplate(template string) string {
re := regexp.MustCompile(`\{\{([^}]+)\}\}`)
return re.ReplaceAllStringFunc(template, func(match string) string {
path := match[2:len(match)-2] // Remove {{}}
return e.getFromContext(path)
})
}
```
## Database Schema (Workflows)
```sql
-- Workflow definitions
CREATE TABLE workflows (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
name VARCHAR(200),
graph JSONB NOT NULL, -- Node/edge structure
input_schema JSONB,
output_schema JSONB,
is_public BOOLEAN DEFAULT false,
tags TEXT[]
);
-- Workflow executions (audit trail)
CREATE TABLE workflow_executions (
id UUID PRIMARY KEY,
workflow_id UUID REFERENCES workflows(id),
input_data JSONB,
output_data JSONB,
status VARCHAR(20), -- running, completed, failed
steps JSONB, -- [{node_id, input, output, duration}]
total_cost_usd NUMERIC(10, 6),
total_time_ms INTEGER,
started_at TIMESTAMP,
completed_at TIMESTAMP
);
```
## Workflow Marketplace
### Shareable Templates
```bash
# Export workflow
POST /api/workflows/{id}/export
→ workflow.json file
# Import workflow
POST /api/workflows/import
← Upload workflow.json
# Publish to marketplace
POST /api/marketplace/publish
{
"workflow_id": "uuid",
"category": "research",
"price": 0, // Free or paid
"license": "MIT"
}
```
### Categories
- 📊 Data Analysis
- 🔍 Research
- 📝 Content Creation
- 💻 Code Review
- 🎓 Education
- 💼 Business Intelligence
- 🔐 Security Audits
## Cost Optimization
### Smart Model Routing in Workflows
```javascript
// Workflow node: Auto-select cheapest capable model
{
"id": "summarize",
"type": "llm_auto",
"config": {
"task": "summarization",
"max_cost": 0.01, // $0.01 max
"min_quality": 0.8, // 80% quality threshold
"prompt": "..."
}
}
// Engine selects:
// GPT-4o-mini ($0.0001/token) ✅
// Not GPT-4 ($0.03/token) - too expensive
```
### Execution Cost Tracking
```sql
-- Track workflow costs
SELECT
w.name,
COUNT(we.id) as executions,
AVG(we.total_cost_usd) as avg_cost,
SUM(we.total_cost_usd) as total_cost
FROM workflows w
JOIN workflow_executions we ON w.id = we.workflow_id
GROUP BY w.id
ORDER BY total_cost DESC;
```
## Advanced Features
### 1. Conditional Branching
```json
{
"id": "quality_check",
"type": "conditional",
"config": {
"condition": "{{verify.confidence}} > 0.8",
"true_branch": "save",
"false_branch": "human_review"
}
}
```
### 2. Loops
```json
{
"id": "process_batch",
"type": "loop",
"config": {
"items": "{{input.files}}",
"body": "extract_data_node",
"merge": "combine_results_node"
}
}
```
### 3. Parallel Execution
```json
{
"id": "parallel_models",
"type": "parallel",
"config": {
"branches": ["gpt4_node", "claude_node", "gemini_node"],
"merge_strategy": "voting" // or "first", "all", "custom"
}
}
```
### 4. Error Handling
```json
{
"id": "api_call",
"type": "tool",
"config": {
"tool": "external_api",
"retry": 3,
"timeout": 5000,
"on_error": "fallback_node"
}
}
```
## Security & Limits
### Sandboxing
- Code execution in isolated containers
- API rate limiting per workflow
- Cost caps per execution
### Permissions
```sql
-- Workflow ACL
CREATE TABLE workflow_permissions (
workflow_id UUID REFERENCES workflows(id),
user_id UUID REFERENCES users(id),
permission VARCHAR(20), -- read, execute, edit, admin
UNIQUE(workflow_id, user_id)
);
```
## API Endpoints
```
POST /api/workflows # Create workflow
GET /api/workflows # List user's workflows
GET /api/workflows/{id} # Get workflow
PUT /api/workflows/{id} # Update workflow
DELETE /api/workflows/{id} # Delete workflow
POST /api/workflows/{id}/execute # Run workflow
GET /api/workflows/{id}/executions # Execution history
GET /api/executions/{id} # Execution details
POST /api/executions/{id}/stop # Cancel execution
GET /api/marketplace/workflows # Browse public workflows
POST /api/marketplace/install/{id} # Install from marketplace
```
## Frontend UI (React/Vue Later, Start Simple)
### Workflow Editor Component
```javascript
// src/js/workflow-editor.js
class WorkflowEditor {
constructor(canvas) {
this.canvas = canvas;
this.nodes = [];
this.edges = [];
}
addNode(type, x, y) {
const node = {
id: generateId(),
type: type,
position: {x, y},
config: {}
};
this.nodes.push(node);
this.render();
}
connectNodes(from, to) {
this.edges.push({from, to});
this.render();
}
execute() {
const workflow = this.toJSON();
fetch('/api/workflows/execute', {
method: 'POST',
body: JSON.stringify(workflow)
}).then(r => r.json()).then(result => {
console.log('Workflow result:', result);
});
}
}
```
## Comparison with Alternatives
| Feature | Chat Switchboard | LangChain | n8n | Zapier |
|---------|-----------------|-----------|-----|--------|
| Visual Builder | ✅ | ❌ | ✅ | ✅ |
| Multi-Model | ✅ | Limited | ❌ | ❌ |
| Open Source | ✅ | ✅ | ✅ | ❌ |
| Self-Hosted | ✅ | N/A | ✅ | ❌ |
| Cost Tracking | ✅ | ❌ | ❌ | ✅ |
| LLM-First | ✅ | ✅ | ❌ | ❌ |
| No-Code | ✅ | ❌ | ✅ | ✅ |
**Unique Position:** LangChain for non-coders + n8n for LLMs
## Roadmap
### MVP (v1.0)
- ✅ Basic DAG execution
- ✅ LLM nodes (single model)
- ✅ Tool nodes (web search, code exec)
- ✅ Input/output nodes
- ✅ Simple UI (form-based, not visual yet)
### v1.1
- ⬜ Visual drag-drop editor
- ⬜ Conditional logic
- ⬜ Loops
- ⬜ Cost tracking
### v1.2
- ⬜ Template marketplace
- ⬜ Workflow sharing
- ⬜ Scheduled executions
- ⬜ Webhook triggers
### v2.0
- ⬜ Collaborative editing
- ⬜ Version control (Git-like)
- ⬜ A/B testing workflows
- ⬜ Analytics dashboard
---
## Get Started
```bash
# Create your first workflow
curl -X POST https://api.chatswitch.example.com/api/workflows \
-H "Authorization: Bearer $TOKEN" \
-d @examples/research-assistant.json
# Execute it
curl -X POST https://api.chatswitch.example.com/api/workflows/{id}/execute \
-d '{"query": "Latest AI research"}'
```
**This is the feature that sets Chat Switchboard apart.**