Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
312 lines
7.0 KiB
Markdown
312 lines
7.0 KiB
Markdown
# Switchboard SDK Reference
|
|
|
|
> `switchboard-sdk.js` — v0.30.2
|
|
|
|
The Switchboard SDK provides a unified API for surfaces and extensions.
|
|
It wraps platform internals (API, Events, Theme, ChatPane, etc.) into a
|
|
single `sw` namespace so authors never need to import platform files
|
|
directly.
|
|
|
|
## Initialization
|
|
|
|
```js
|
|
// Automatic — the SDK initializes on page load.
|
|
// Access via the global `sw` alias:
|
|
sw.api.get('/api/v1/channels').then(console.log);
|
|
```
|
|
|
|
The SDK emits a `sw:ready` CustomEvent on `document` when initialized.
|
|
Extensions loaded after boot can listen for it:
|
|
|
|
```js
|
|
document.addEventListener('sw:ready', (e) => {
|
|
const sw = e.detail.sw;
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## sw.user
|
|
|
|
Current authenticated user (read-only getter).
|
|
|
|
```js
|
|
const u = sw.user;
|
|
// { id, username, display_name, email, role, avatar }
|
|
```
|
|
|
|
Returns `null` if not authenticated.
|
|
|
|
## sw.isAdmin
|
|
|
|
`true` if the current user has the `admin` role.
|
|
|
|
---
|
|
|
|
## sw.api
|
|
|
|
REST client with automatic auth header injection and 401 retry.
|
|
|
|
| Method | Signature | Returns |
|
|
|--------|-----------|---------|
|
|
| `get` | `(path, opts?)` | `Promise<object>` |
|
|
| `post` | `(path, body, opts?)` | `Promise<object>` |
|
|
| `put` | `(path, body, opts?)` | `Promise<object>` |
|
|
| `del` | `(path, opts?)` | `Promise<object>` |
|
|
| `stream` | `(path, body, signal?)` | `Promise<Response>` |
|
|
|
|
`opts` accepts `{ signal: AbortSignal }` for cancellation.
|
|
|
|
### Examples
|
|
|
|
```js
|
|
// List channels
|
|
const channels = await sw.api.get('/api/v1/channels');
|
|
|
|
// Create a channel
|
|
const ch = await sw.api.post('/api/v1/channels', {
|
|
title: 'Support',
|
|
type: 'direct'
|
|
});
|
|
|
|
// Streaming completion
|
|
const resp = await sw.api.stream('/api/v1/completions', {
|
|
channel_id: ch.id,
|
|
message: 'Hello'
|
|
});
|
|
const reader = resp.body.getReader();
|
|
```
|
|
|
|
---
|
|
|
|
## sw.on / sw.once / sw.off / sw.emit
|
|
|
|
EventBus integration. Supports wildcard patterns.
|
|
|
|
```js
|
|
// Subscribe
|
|
const unsub = sw.on('chat.message.*', (payload) => {
|
|
console.log('New message:', payload);
|
|
});
|
|
|
|
// One-shot
|
|
sw.once('channel.created', (ch) => { ... });
|
|
|
|
// Unsubscribe
|
|
sw.off('chat.message.*', handler);
|
|
|
|
// Emit
|
|
sw.emit('custom.event', { data: 123 });
|
|
```
|
|
|
|
### Common Events
|
|
|
|
| Event | Payload | Description |
|
|
|-------|---------|-------------|
|
|
| `chat.message.sent` | `{ message }` | User sent a message |
|
|
| `chat.message.received` | `{ message }` | AI response received |
|
|
| `channel.created` | `{ channel }` | New channel created |
|
|
| `channel.switched` | `{ channelId }` | Active channel changed |
|
|
| `theme.changed` | `{ theme }` | Theme mode changed |
|
|
|
|
---
|
|
|
|
## sw.theme
|
|
|
|
Theme observation and control.
|
|
|
|
| Property/Method | Type | Description |
|
|
|-----------------|------|-------------|
|
|
| `current` | `string` (getter) | Resolved theme: `'dark'` or `'light'` |
|
|
| `mode` | `string` (getter) | User preference: `'dark'`, `'light'`, or `'system'` |
|
|
| `set(mode)` | `void` | Set theme mode |
|
|
| `on('change', fn)` | `() => void` | Subscribe to theme changes; returns unsubscribe fn |
|
|
|
|
```js
|
|
// React to theme changes
|
|
const unsub = sw.theme.on('change', (resolved) => {
|
|
document.body.classList.toggle('dark', resolved === 'dark');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## sw.toast / sw.confirm / sw.modal
|
|
|
|
UI primitives for notifications and dialogs.
|
|
|
|
```js
|
|
// Toast notification
|
|
sw.toast('Saved successfully', 'success'); // types: success, error, info, warn
|
|
sw.toast('Something went wrong', 'error');
|
|
|
|
// Confirmation dialog (returns Promise<boolean>)
|
|
const ok = await sw.confirm('Delete this item?');
|
|
|
|
// Modal
|
|
sw.modal.open(htmlContentOrElementId);
|
|
sw.modal.close(id);
|
|
```
|
|
|
|
---
|
|
|
|
## sw.chat(container, opts)
|
|
|
|
Mount a ChatPane instance into a DOM element.
|
|
|
|
```js
|
|
const pane = sw.chat(document.getElementById('my-chat'), {
|
|
channelId: 'abc-123',
|
|
standalone: true, // default: true
|
|
});
|
|
|
|
// pane.renderMessages(), pane.destroy(), etc.
|
|
```
|
|
|
|
**Options:**
|
|
|
|
| Key | Type | Default | Description |
|
|
|-----|------|---------|-------------|
|
|
| `channelId` | `string` | `null` | Channel to load |
|
|
| `standalone` | `boolean` | `true` | Standalone mode (own input area) |
|
|
|
|
---
|
|
|
|
## sw.notes(container, opts)
|
|
|
|
Mount a NotePanel instance into a DOM element.
|
|
|
|
```js
|
|
const panel = sw.notes(document.getElementById('my-notes'), {
|
|
projectId: 'proj-123',
|
|
});
|
|
|
|
// panel.loadNotesList(), panel.openNoteEditor(id), panel.destroy()
|
|
```
|
|
|
|
---
|
|
|
|
## sw.panels()
|
|
|
|
Access the PanelRegistry for sidebar panel management.
|
|
|
|
```js
|
|
const panels = sw.panels();
|
|
panels.open('notes');
|
|
panels.toggle('editor');
|
|
panels.isOpen('notes'); // boolean
|
|
panels.active(); // current panel name or null
|
|
panels.cycle(); // cycle to next panel
|
|
panels.register('custom', { ... });
|
|
```
|
|
|
|
---
|
|
|
|
## sw.workflow
|
|
|
|
Workflow stage surface management (v0.30.2).
|
|
|
|
### sw.workflow(container, opts)
|
|
|
|
Mount a workflow stage surface.
|
|
|
|
```js
|
|
sw.workflow(document.getElementById('stage-mount'), {
|
|
channelId: 'ch-abc',
|
|
stageMode: 'form_only', // chat_only | form_only | form_chat | review
|
|
surfacePkgId: 'my-surface', // optional: custom package surface
|
|
formTemplate: { fields: [...] },
|
|
});
|
|
```
|
|
|
|
### sw.workflow.registerSurface(name, factory)
|
|
|
|
Register a custom stage surface from a package.
|
|
|
|
```js
|
|
sw.workflow.registerSurface('intake-form', (container, ctx) => {
|
|
// ctx: { channelId, basePath, stageMode, formTemplate, ... }
|
|
container.innerHTML = '<h2>Custom Intake</h2>';
|
|
return {
|
|
mount() { /* called on attach */ },
|
|
unmount() { /* called on detach */ },
|
|
};
|
|
});
|
|
```
|
|
|
|
### sw.workflow.getContext(channelId)
|
|
|
|
Fetch workflow instance status.
|
|
|
|
```js
|
|
const status = await sw.workflow.getContext('ch-abc');
|
|
// { workflow_id, current_stage, stage_data, ... }
|
|
```
|
|
|
|
### sw.workflow.advance(channelId, data?)
|
|
|
|
Advance to the next stage, optionally merging data.
|
|
|
|
```js
|
|
await sw.workflow.advance('ch-abc', { approved: true });
|
|
```
|
|
|
|
### sw.workflow.reject(channelId, reason)
|
|
|
|
Reject the current stage (go back).
|
|
|
|
```js
|
|
await sw.workflow.reject('ch-abc', 'Missing required documents');
|
|
```
|
|
|
|
---
|
|
|
|
## sw.pipe
|
|
|
|
Filter pipeline for intercepting chat messages at three stages:
|
|
pre-send, post-receive (stream), and post-render.
|
|
|
|
### Registration
|
|
|
|
```js
|
|
// Pre-send filter (runs before message is sent to API)
|
|
sw.pipe.pre(50, (ctx) => {
|
|
// ctx: { message, channel, metadata }
|
|
ctx.message += '\n\n[via my extension]';
|
|
return ctx; // return ctx to continue, null to halt
|
|
});
|
|
|
|
// Stream filter (runs on each SSE chunk)
|
|
sw.pipe.stream(50, (ctx) => {
|
|
// ctx: { chunk, channel, accumulated }
|
|
return ctx;
|
|
});
|
|
|
|
// Render filter (runs after markdown rendering)
|
|
sw.pipe.render(50, (ctx) => {
|
|
// ctx: { html, message, channel }
|
|
ctx.html = ctx.html.replace(/TODO/g, '<mark>TODO</mark>');
|
|
return ctx;
|
|
});
|
|
```
|
|
|
|
**Priority:** lower numbers run first. Convention: 0-49 system, 50-99
|
|
extensions, 100+ user customizations.
|
|
|
|
**Scope:** restrict a filter to specific channel types:
|
|
|
|
```js
|
|
sw.pipe.render(50, myFilter, {
|
|
scope: { channelType: ['direct', 'group'] },
|
|
source: 'my-extension',
|
|
});
|
|
```
|
|
|
|
### Introspection
|
|
|
|
```js
|
|
sw.pipe.list();
|
|
// { pre: [...], stream: [...], render: [...] }
|
|
// Each entry: { priority, source, scope, calls, avgMs, errors }
|
|
```
|