Add HTML/Frontend extensions architecture doc
This commit is contained in:
445
docs/HTML_EXTENSIONS.md
Normal file
445
docs/HTML_EXTENSIONS.md
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
# 🎨 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.**
|
||||||
Reference in New Issue
Block a user