import type { InterventionOutcome, HermesJob, HermesJobEvent, MemberWorkHistory, Pod, PodActivityEvent, PodInput, } from '@podman/shared'; const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || (import.meta.env.DEV || ['localhost', '127.0.0.1'].includes(window.location.hostname) ? 'http://localhost:8787' : ''); export interface MemoryStats { observations: number; collisions: number; interventions: number; outcomes: number; } export interface LiveConversationSession { sessionId: string; podId: string; identity: string; displayName: string; room: string; url: string; token: string; startedAt: string; lastEventAt?: string; endedAt?: string; } async function json(res: Response): Promise { if (!res.ok) { const body = (await res.json().catch(() => ({}))) as { error?: string }; throw new Error(body.error || `request failed: ${res.status}`); } return res.json() as Promise; } /** Mint a LiveKit token from the backend. */ export async function fetchToken(params: { room: string; identity: string; name: string; githubLogin?: string; }): Promise<{ token: string; url: string }> { const res = await fetch(`${BACKEND_URL}/api/token`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(params), }); return json(res); } /** Record an intervention outcome for the policy learning loop. */ export async function postOutcome(outcome: InterventionOutcome): Promise { const res = await fetch(`${BACKEND_URL}/api/outcome`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(outcome), }); if (!res.ok) throw new Error(`outcome post failed: ${res.status}`); } export async function createSyncPr(input: { headBranch?: string; file?: string; summary?: string; }): Promise<{ url: string; number: number }> { return json( await fetch(`${BACKEND_URL}/api/sync-pr`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(input), }), ); } // --- Pods CRUD --- export async function listPods(): Promise { return json(await fetch(`${BACKEND_URL}/api/pods`)); } /** Display names currently connected per pod id (= LiveKit room name). */ export async function getPresence(): Promise> { return json(await fetch(`${BACKEND_URL}/api/presence`)); } export async function getMemoryStats(): Promise { return json(await fetch(`${BACKEND_URL}/api/memory/stats`)); } export async function getPodActivity(id: string, limit = 80): Promise { return json( await fetch(`${BACKEND_URL}/api/pods/${encodeURIComponent(id)}/activity?limit=${limit}`), ); } export function podActivityStreamUrl(id: string): string { return `${BACKEND_URL}/api/pods/${encodeURIComponent(id)}/activity/stream`; } /** URL of the pod's generated background-music MP3 (looped client-side). */ export function podMusicUrl(id: string): string { return `${BACKEND_URL}/api/pods/${encodeURIComponent(id)}/music`; } export async function getMemberWorkHistory( podId: string, member: string, ): Promise { return json( await fetch( `${BACKEND_URL}/api/pods/${encodeURIComponent(podId)}/members/${encodeURIComponent( member, )}/history?hours=24&limit=80`, ), ); } export async function createPod(input: PodInput): Promise { return json( await fetch(`${BACKEND_URL}/api/pods`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(input), }), ); } export async function updatePod(id: string, patch: PodInput): Promise { return json( await fetch(`${BACKEND_URL}/api/pods/${encodeURIComponent(id)}`, { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify(patch), }), ); } export async function deletePod(id: string): Promise { const res = await fetch(`${BACKEND_URL}/api/pods/${encodeURIComponent(id)}`, { method: 'DELETE', }); if (!res.ok) throw new Error(`delete pod failed: ${res.status}`); } export async function addMember(id: string, name: string): Promise { return json( await fetch(`${BACKEND_URL}/api/pods/${encodeURIComponent(id)}/members`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name }), }), ); } export async function testPodVoice(id: string): Promise { const res = await fetch(`${BACKEND_URL}/api/pods/${encodeURIComponent(id)}/voice-test`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ message: 'PodMan voice test. Gemini TTS is playing through LiveKit.', }), }); if (!res.ok) throw new Error(`voice test failed: ${res.status}`); } export async function startLiveConversation( podId: string, input: { identity: string; displayName?: string }, ): Promise { return json( await fetch(`${BACKEND_URL}/api/pods/${encodeURIComponent(podId)}/live-conversation/start`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(input), }), ); } export async function stopLiveConversation(podId: string, sessionId: string): Promise { const res = await fetch( `${BACKEND_URL}/api/pods/${encodeURIComponent( podId, )}/live-conversation/${encodeURIComponent(sessionId)}/stop`, { method: 'POST' }, ); if (!res.ok) throw new Error(`live conversation stop failed: ${res.status}`); } export async function getLiveConversationHermesJob( podId: string, sessionId: string, ): Promise<{ job: HermesJob | null; events: HermesJobEvent[] }> { return json( await fetch( `${BACKEND_URL}/api/pods/${encodeURIComponent( podId, )}/live-conversation/${encodeURIComponent(sessionId)}/hermes-job`, ), ); } export async function abortLiveConversationHermesJob( podId: string, sessionId: string, ): Promise<{ job: HermesJob | null }> { return json( await fetch( `${BACKEND_URL}/api/pods/${encodeURIComponent( podId, )}/live-conversation/${encodeURIComponent(sessionId)}/hermes-job/abort`, { method: 'POST' }, ), ); } export async function removeMember(id: string, name: string): Promise { return json( await fetch( `${BACKEND_URL}/api/pods/${encodeURIComponent(id)}/members/${encodeURIComponent(name)}`, { method: 'DELETE' }, ), ); }