Changeset 0.30.2 cs2 (#202)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit is contained in:
311
docs/SDK.md
Normal file
311
docs/SDK.md
Normal file
@@ -0,0 +1,311 @@
|
||||
# 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 }
|
||||
```
|
||||
Reference in New Issue
Block a user