Files
links/static_src/jbot/services/api.js
T
junvandCopilot 2c41b46da0 jbot: cancel button + abort + better Hermes error message
- VoiceButton gets a red ✕ cancel button that appears during thinking/speaking
- AbortController per pipeline run — ✕ aborts the in-flight apiChat fetch,
  stops TTS playback, and returns to idle cleanly
- Hermes connection-refused error shows readable message instead of raw stack
- apiChat() accepts optional AbortSignal (4th arg), passed from App.jsx
- handleSpaceUp dep array updated

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-17 14:13:37 +10:00

94 lines
2.7 KiB
JavaScript

// Django backend at /api/jbot/ — no separate Node server needed
const BASE = '/api/jbot';
export async function apiConfig() {
const r = await fetch(`${BASE}/config/`);
return r.json();
}
// Send conversation history + optional long-term memories + settings, returns { text }
export async function apiChat(messages, memories = [], settings = {}, signal) {
const r = await fetch(`${BASE}/chat/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages, memories, settings }),
signal,
});
if (!r.ok) {
const e = await r.json().catch(() => ({}));
throw new Error(e.error || `HTTP ${r.status}`);
}
return r.json();
}
// Send base64 audio blob, returns { text }
export async function apiAsr(audioBase64, format = 'audio/webm') {
const r = await fetch(`${BASE}/asr/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ audioBase64, format }),
});
if (!r.ok) {
const e = await r.json().catch(() => ({}));
throw new Error(e.error || `HTTP ${r.status}`);
}
return r.json();
}
// Send text + optional voice override, returns { audioBase64, format }
export async function apiTts(text, voice) {
const r = await fetch(`${BASE}/tts/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, ...(voice ? { voice } : {}) }),
});
if (!r.ok) {
const e = await r.json().catch(() => ({}));
throw new Error(e.error || `HTTP ${r.status}`);
}
return r.json();
}
// Extract memorable facts from one conversation exchange.
// Returns string[] — never throws (returns [] on any error).
export async function apiExtractMemory(userText, botText) {
try {
const r = await fetch(`${BASE}/memory/extract/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userText, botText }),
});
if (!r.ok) return [];
const { facts = [] } = await r.json();
return facts;
} catch {
return [];
}
}
// Read JBOT API credentials/settings from server (tokens are masked as '***')
export async function apiGetApiConfig() {
try {
const r = await fetch(`${BASE}/api-config/`);
if (!r.ok) return {};
return r.json();
} catch {
return {};
}
}
// Save JBOT API credentials/settings to server DB
// Pass '***' for secret fields to leave them unchanged
export async function apiSaveApiConfig(config) {
const r = await fetch(`${BASE}/api-config/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
});
if (!r.ok) {
const e = await r.json().catch(() => ({}));
throw new Error(e.error || `HTTP ${r.status}`);
}
return r.json();
}