const MAX_RETRIES = 3 const RETRY_DELAY_MS = 2000 async function fetchWithRetry(url, options, retries = MAX_RETRIES) { for (let attempt = 1; attempt <= retries; attempt++) { try { const res = await fetch(url, options) if (res.status === 502 && attempt < retries) { // Bridge unreachable — wait and retry await new Promise(r => setTimeout(r, RETRY_DELAY_MS * attempt)) continue } return res } catch (err) { if (attempt >= retries) throw err await new Promise(r => setTimeout(r, RETRY_DELAY_MS * attempt)) } } } export async function sendMessage(text, characterId = null, promptStyle = null, conversationId = null) { const payload = { message: text, agent: 'main' } if (characterId) payload.character_id = characterId if (promptStyle) payload.prompt_style = promptStyle if (conversationId) payload.conversation_id = conversationId const res = await fetchWithRetry('/api/agent/message', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) if (!res.ok) { const err = await res.json().catch(() => ({ error: 'Request failed' })) throw new Error(err.error || `HTTP ${res.status}`) } const data = await res.json() return { response: data.response, model: data.model || null, prompt_style: data.prompt_style || null } } export async function getPromptStyles() { const res = await fetch('/api/prompt-styles') if (!res.ok) return [] return await res.json() } export async function getActiveStyle() { const res = await fetch('/api/prompt-style') if (!res.ok) return { style: 'standard' } return await res.json() } export async function setActiveStyle(style) { const res = await fetch('/api/prompt-style', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ style }), }) if (!res.ok) throw new Error('Failed to set prompt style') return await res.json() } export async function synthesize(text, voice, engine = 'kokoro', model = null) { const payload = { text, voice, engine } if (model) payload.model = model const res = await fetch('/api/tts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) if (!res.ok) throw new Error('TTS failed') return await res.arrayBuffer() } export async function transcribe(wavBlob) { const res = await fetch('/api/stt', { method: 'POST', headers: { 'Content-Type': 'audio/wav' }, body: wavBlob, }) if (!res.ok) throw new Error('STT failed') const data = await res.json() return data.text } export async function healthCheck() { try { const res = await fetch('/api/health?url=' + encodeURIComponent('http://localhost:8081/'), { signal: AbortSignal.timeout(5000) }) const data = await res.json() return data.status === 'online' } catch { return false } }