Changeset 0.6.0 (#36)

This commit is contained in:
2026-02-20 22:24:47 +00:00
parent 30d0c11219
commit 925b70f98c
34 changed files with 2575 additions and 637 deletions

View File

@@ -1,10 +1,17 @@
// ==========================================
// Chat Switchboard API Client (v0.5.0)
// Chat Switchboard API Client (v0.6.2)
// ==========================================
// Backend-only mode. Handles auth tokens and
// all HTTP calls. No offline fallback.
//
// BASE_PATH: injected via index.html <script>
// at container startup. All API paths are
// prefixed automatically.
// ==========================================
const BASE = window.__BASE__ || '';
const _storageKey = BASE ? `sb_auth_${BASE.replace(/\//g, '')}` : 'sb_auth';
const API = {
accessToken: null,
refreshToken: null,
@@ -15,7 +22,7 @@ const API = {
loadTokens() {
try {
const saved = JSON.parse(localStorage.getItem('sb_auth') || 'null');
const saved = JSON.parse(localStorage.getItem(_storageKey) || 'null');
if (saved) {
this.accessToken = saved.accessToken || null;
this.refreshToken = saved.refreshToken || null;
@@ -25,7 +32,7 @@ const API = {
},
saveTokens() {
localStorage.setItem('sb_auth', JSON.stringify({
localStorage.setItem(_storageKey, JSON.stringify({
accessToken: this.accessToken,
refreshToken: this.refreshToken,
user: this.user
@@ -36,7 +43,7 @@ const API = {
this.accessToken = null;
this.refreshToken = null;
this.user = null;
localStorage.removeItem('sb_auth');
localStorage.removeItem(_storageKey);
},
get isAuthed() { return !!this.accessToken; },
@@ -45,7 +52,7 @@ const API = {
// ── Auth ─────────────────────────────────
async health() {
const resp = await fetch('/api/v1/health', { signal: AbortSignal.timeout(8000) });
const resp = await fetch(BASE + '/api/v1/health', { signal: AbortSignal.timeout(8000) });
if (!resp.ok) throw new Error(`Health: ${resp.status}`);
return resp.json();
},
@@ -58,7 +65,7 @@ const API = {
async register(username, email, password) {
const data = await this._post('/api/v1/auth/register', { username, email, password }, true);
this._setAuth(data);
if (!data.pending) this._setAuth(data);
return data;
},
@@ -92,36 +99,39 @@ const API = {
this.saveTokens();
},
// ── Chats ────────────────────────────────
// ── Channels ──────────────────────────────
listChats(page = 1, perPage = 100) {
return this._get(`/api/v1/chats?page=${page}&per_page=${perPage}`);
listChannels(page = 1, perPage = 100, type = '') {
let url = `/api/v1/channels?page=${page}&per_page=${perPage}`;
if (type) url += `&type=${type}`;
return this._get(url);
},
createChat(title, model, systemPrompt) {
const body = { title };
createChannel(title, model, systemPrompt, type = 'direct') {
const body = { title, type };
if (model) body.model = model;
if (systemPrompt) body.system_prompt = systemPrompt;
return this._post('/api/v1/chats', body);
return this._post('/api/v1/channels', body);
},
getChat(id) { return this._get(`/api/v1/chats/${id}`); },
updateChat(id, updates) { return this._put(`/api/v1/chats/${id}`, updates); },
deleteChat(id) { return this._del(`/api/v1/chats/${id}`); },
getChannel(id) { return this._get(`/api/v1/channels/${id}`); },
updateChannel(id, updates) { return this._put(`/api/v1/channels/${id}`, updates); },
deleteChannel(id) { return this._del(`/api/v1/channels/${id}`); },
// ── Messages ─────────────────────────────
listMessages(chatId, page = 1, perPage = 200) {
return this._get(`/api/v1/chats/${chatId}/messages?page=${page}&per_page=${perPage}`);
listMessages(channelId, page = 1, perPage = 200) {
return this._get(`/api/v1/channels/${channelId}/messages?page=${page}&per_page=${perPage}`);
},
// ── Completions ──────────────────────────
async streamCompletion(chatId, content, model, signal) {
const body = { chat_id: chatId, content, stream: true };
async streamCompletion(channelId, content, model, signal, apiConfigId) {
const body = { channel_id: channelId, content, stream: true };
if (model) body.model = model;
if (apiConfigId) body.api_config_id = apiConfigId;
// Only send max_tokens if user explicitly set it (non-zero = override)
if (App.settings.maxTokens > 0) body.max_tokens = App.settings.maxTokens;
let resp = await fetch('/api/v1/chat/completions', {
let resp = await fetch(BASE + '/api/v1/chat/completions', {
method: 'POST',
headers: this._authHeaders(),
body: JSON.stringify(body),
@@ -130,7 +140,7 @@ const API = {
if (resp.status === 401 && this.refreshToken) {
if (await this.refresh()) {
resp = await fetch('/api/v1/chat/completions', {
resp = await fetch(BASE + '/api/v1/chat/completions', {
method: 'POST',
headers: this._authHeaders(),
body: JSON.stringify(body),
@@ -182,6 +192,7 @@ const API = {
adminToggleActive(id, active) { return this._put(`/api/v1/admin/users/${id}/active`, { is_active: active }); },
adminDeleteUser(id) { return this._del(`/api/v1/admin/users/${id}`); },
adminGetSettings() { return this._get('/api/v1/admin/settings'); },
getPublicSettings() { return this._get('/api/v1/settings/public'); },
adminUpdateSetting(key, value) { return this._put(`/api/v1/admin/settings/${key}`, value); },
adminGetStats() { return this._get('/api/v1/admin/stats'); },
@@ -196,8 +207,12 @@ const API = {
adminListModels() { return this._get('/api/v1/admin/models'); },
adminFetchModels() { return this._post('/api/v1/admin/models/fetch', {}); },
adminUpdateModel(id, updates) { return this._put(`/api/v1/admin/models/${id}`, updates); },
adminBulkUpdateModels(isEnabled) { return this._put('/api/v1/admin/models/bulk', { is_enabled: isEnabled }); },
adminDeleteModel(id) { return this._del(`/api/v1/admin/models/${id}`); },
// ── User Models ──────────────────────────
listEnabledModels() { return this._get('/api/v1/models/enabled'); },
// ── HTTP Internals ───────────────────────
_authHeaders() {
@@ -230,7 +245,7 @@ const API = {
if (auth) opts.headers['Authorization'] = `Bearer ${this.accessToken}`;
if (body) opts.body = JSON.stringify(body);
const resp = await fetch(path, opts);
const resp = await fetch(BASE + path, opts);
if (!resp.ok) {
const data = await resp.json().catch(() => ({}));
const err = new Error(data.error || `HTTP ${resp.status}`);