// Typed client for the FastAPI backend. // Backend URL is configurable via NEXT_PUBLIC_BACKEND_URL so we can point at // localhost in dev and at Render in production. // In production (HF Spaces deploy): empty -> same-origin requests, no CORS. // In dev: set NEXT_PUBLIC_BACKEND_URL=http://localhost:8000 in frontend/.env.local export const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL ?? ""; export type Citation = { policy_id: string; policy_name: string; insurer_slug: string; page_start: number; page_end: number; source_url: string; score: number; }; export type ChatResponse = { reply_text: string; citations: Citation[]; brain_used: string; intent: string; language: string; latency_ms: number; session_id: string; audio_base64?: string | null; faithfulness_passed?: boolean; faithfulness_reasons?: string[]; blocked?: boolean; }; export type ChatMessage = { role: "user" | "assistant"; content: string; }; export async function postChat(args: { user_text: string; session_id?: string; chat_history?: ChatMessage[]; profile?: Record; policy_filter_ids?: string[]; return_audio?: boolean; tts_language_code?: string; }): Promise { const resp = await fetch(`${BACKEND_URL}/api/chat`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ user_text: args.user_text, session_id: args.session_id, chat_history: args.chat_history ?? [], profile: args.profile ?? {}, policy_filter_ids: args.policy_filter_ids, return_audio: args.return_audio ?? false, tts_language_code: args.tts_language_code ?? "en-IN", }), }); if (!resp.ok) { const t = await resp.text(); throw new Error(`chat failed: ${resp.status} ${t}`); } return resp.json(); } export async function postTranscribe( blob: Blob, language_code?: string, ): Promise<{ text: string; language_code?: string; latency_ms: number }> { const fd = new FormData(); // Use blob's mime to derive extension; default to wav const mime = blob.type || "audio/wav"; const ext = mime.includes("webm") ? "webm" : mime.includes("mp3") ? "mp3" : mime.includes("ogg") ? "ogg" : "wav"; fd.append("file", blob, `audio.${ext}`); if (language_code) fd.append("language_code", language_code); const resp = await fetch(`${BACKEND_URL}/api/transcribe`, { method: "POST", body: fd, }); if (!resp.ok) { const t = await resp.text(); throw new Error(`transcribe failed: ${resp.status} ${t}`); } return resp.json(); } export async function getHealth(): Promise<{ status: string; providers_ok: Record; missing_keys: string[]; }> { const resp = await fetch(`${BACKEND_URL}/api/health`); if (!resp.ok) throw new Error(`health failed: ${resp.status}`); return resp.json(); } export type PolicyEntry = { name: string; source_url: string; }; export type CoverageInsurer = { slug: string; name: string; home_url: string; policy_count: number; sample_policies: PolicyEntry[]; }; export type CoverageResponse = { total_chunks: number; total_policies: number; total_insurers: number; insurers: CoverageInsurer[]; }; export async function getCoverage(): Promise { const resp = await fetch(`${BACKEND_URL}/api/coverage`); if (!resp.ok) throw new Error(`coverage failed: ${resp.status}`); return resp.json(); } export type UploadResponse = { policy_id: string; policy_name: string; chunks_added: number; pages_indexed: number; elapsed_ms: number; }; export type ScorecardSubScore = { name: string; score: number; summary: string; signals: string[]; }; export type ScorecardResponse = { policy_id: string; policy_name: string; insurer_slug: string; overall_score: number; grade: string; one_liner: string; sub_scores: ScorecardSubScore[]; data_completeness_pct: number; methodology_link: string; }; export async function getScorecard(policy_id: string): Promise { const resp = await fetch(`${BACKEND_URL}/api/policies/${encodeURIComponent(policy_id)}/scorecard`); if (!resp.ok) { const t = await resp.text(); throw new Error(`scorecard failed: ${resp.status} ${t}`); } return resp.json(); } export type PremiumEstimateRequest = { age: number; sum_insured_inr: number; city_tier?: "metro" | "tier1" | "tier2"; smoker?: boolean; family_size?: number; policy_id?: string | null; }; export type PremiumEstimateResponse = { policy_id: string; point_estimate_inr: number; low_inr: number; high_inr: number; methodology: string; sources: string[]; is_illustrative: boolean; disclaimer: string; }; export async function postPremiumEstimate(req: PremiumEstimateRequest): Promise { const resp = await fetch(`${BACKEND_URL}/api/premium/estimate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...req, city_tier: req.city_tier ?? "metro", smoker: req.smoker ?? false, family_size: req.family_size ?? 1, }), }); if (!resp.ok) throw new Error(`premium estimate failed: ${resp.status}`); return resp.json(); } export async function uploadPolicy(file: File): Promise { const fd = new FormData(); fd.append("file", file); const resp = await fetch(`${BACKEND_URL}/api/upload-policy`, { method: "POST", body: fd, }); if (!resp.ok) { const t = await resp.text(); throw new Error(`upload failed: ${resp.status} ${t}`); } return resp.json(); } // Decode a base64 string to a playable audio Blob URL. export function audioBlobURLFromBase64(b64: string, mime = "audio/wav"): string { const bin = atob(b64); const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); const blob = new Blob([bytes], { type: mime }); return URL.createObjectURL(blob); }