// frontend/api.jsx — backend client + demo-shape adapter. // Served from / by FastAPI StaticFiles, so same origin — no CORS. const API_BASE = "/api"; async function authHeaders() { const getToken = window.getAuthToken; if (!getToken) return {}; const token = await getToken(); return token ? { Authorization: `Bearer ${token}` } : {}; } async function createJob(blob, filename, formKind) { const fd = new FormData(); fd.append("form_kind", formKind); fd.append("audio", blob, filename); const resp = await fetch(`${API_BASE}/jobs`, { method: "POST", body: fd, headers: await authHeaders(), }); if (!resp.ok) { const detail = await resp.text(); throw new Error(`POST /api/jobs ${resp.status}: ${detail}`); } return resp.json(); } async function getJob(jobId) { const resp = await fetch(`${API_BASE}/jobs/${jobId}`, { headers: await authHeaders(), }); if (resp.status === 404) throw new Error("job not found"); if (!resp.ok) throw new Error(`GET /api/jobs ${resp.status}`); return resp.json(); } // Poll every intervalMs until status is done or failed, calling onState(state) // on every tick. Abortable via AbortSignal. async function pollJob(jobId, { intervalMs = 2000, signal, onState } = {}) { while (true) { if (signal && signal.aborted) throw new Error("polling aborted"); const state = await getJob(jobId); if (onState) onState(state); if (state.status === "done" || state.status === "failed") return state; await new Promise(r => setTimeout(r, intervalMs)); } } // Adapt flat backend payload into the frontend's {value, c, note} shape. function adaptBackend(state) { const fields = state.extracted_fields || {}; const conf = state.field_confidence || {}; const notes = state.field_notes || {}; const rejected = state.rejected_values || {}; const lowConf = new Set(state.low_confidence_fields || []); const out = {}; for (const [key, value] of Object.entries(fields)) { if (key === "itr_filing_status") continue; // pulled out separately let c = conf[key]; if (c === undefined) c = value == null ? null : 0.95; let note = notes[key] || null; if (!note && rejected[key] != null) note = `Rejected: ${rejected[key]}`; const status = lowConf.has(key) ? "low" : undefined; out[key] = { value, c, note, status }; } return { transcript: state.transcript, durationSec: state.duration_sec, fields: out, missingRequired: state.missing_required_fields || [], itr: fields.itr_filing_status || null, }; } Object.assign(window, { createJob, getJob, pollJob, adaptBackend });