// API client for InnerVoice backend // In production/HuggingFace, we want empty string to use relative /api paths which hits the Next.js rewrite proxy. const API_URL = process.env.NEXT_PUBLIC_API_URL || ""; function getAuthHeader(): Record { if (typeof window === "undefined") return {}; const token = localStorage.getItem("token"); return token ? { Authorization: `Bearer ${token}` } : {}; } export async function apiGet(path: string): Promise { const res = await fetch(`${API_URL}${path}`, { headers: { ...getAuthHeader() }, }); if (!res.ok) throw new Error(`API Error ${res.status}: ${await res.text()}`); return res.json(); } export async function apiPost(path: string, body: unknown): Promise { const res = await fetch(`${API_URL}${path}`, { method: "POST", headers: { "Content-Type": "application/json", ...getAuthHeader() }, body: JSON.stringify(body), }); if (!res.ok) throw new Error(`API Error ${res.status}: ${await res.text()}`); return res.json(); } export async function apiPut(path: string, body?: unknown): Promise { const res = await fetch(`${API_URL}${path}`, { method: "PUT", headers: { "Content-Type": "application/json", ...getAuthHeader() }, body: body ? JSON.stringify(body) : undefined, }); if (!res.ok) throw new Error(`API Error ${res.status}: ${await res.text()}`); return res.json(); } export async function apiDelete(path: string): Promise { const res = await fetch(`${API_URL}${path}`, { method: "DELETE", headers: { ...getAuthHeader() }, }); if (!res.ok) throw new Error(`API Error ${res.status}: ${await res.text()}`); return res.json(); } // ── Typed API functions ─────────────────────────────────────────────────────── export interface VoiceEntry { id: string; created_at: string; primary_emotion: string; emotion_confidence: number; energy_score: number; calmness_score: number; mood_score: number; clarity_score: number; transcription: string; duration_seconds: number; pitch_mean: number; speech_rate: number; pause_count: number; filler_rate: number; } export interface MoodAlert { id: string; created_at: string; alert_type: string; severity: string; message: string; suggested_action: string | null; is_read: boolean; } export interface TrendsData { entries_count: number; streak: number; most_common_emotion: string | null; this_week: Record; last_week: Record; insights: string[]; } export interface AnalyzeResult { entry_id: string; emotion: string; confidence: number; mood_scores: { energy: number; calmness: number; mood: number; clarity: number }; transcription: string; features: Record; insight: string; new_alerts: MoodAlert[]; } export interface ChatChannel { id: string; title: string; created_at: string; updated_at: string; message_count: number; } export const api = { getEntries: (days = 30) => apiGet(`/api/entries?days=${days}`), getTrends: () => apiGet(`/api/trends`), getAlerts: () => apiGet(`/api/alerts`), markAlertRead: (alertId: string) => apiPut<{ success: boolean }>(`/api/alerts/${alertId}/read`), chat: (message: string, channelId: string) => apiPost<{ response: string; message_id: string }>("/api/chat", { message, channel_id: channelId }), getChatHistory: (channelId: string) => apiGet>( `/api/chat/history?channel_id=${channelId}` ), getChannels: () => apiGet("/api/chat/channels"), createChannel: (title = "New Chat") => apiPost("/api/chat/channels", { title }), renameChannel: (channelId: string, title: string) => apiPut<{ success: boolean; title: string }>(`/api/chat/channels/${channelId}`, { title }), deleteChannel: (channelId: string) => fetch(`${API_URL}/api/chat/channels/${channelId}`, { method: "DELETE", headers: { ...getAuthHeader() }, }).then(r => r.json()), getWeeklyReport: () => apiGet>("/api/weekly-report"), getDailyPrompt: () => apiGet<{ prompt: string; context: string }>("/api/daily-prompt"), logSleep: (entryId: string, sleepHours: number) => apiPost<{ success: boolean; sleep_hours: number }>(`/api/entries/${entryId}/sleep`, { sleep_hours: sleepHours }), getSleepCorrelation: () => apiGet>("/api/sleep-correlation"), inviteTrustedMember: (email: string) => apiPost<{ success: boolean; message: string }>("/api/trusted-circle/invite", { email }), getTrustedMembers: () => apiGet<{ id: string; email: string; joined_at: string }[]>("/api/trusted-circle"), removeTrustedMember: (id: string) => apiDelete<{ success: boolean; message: string }>(`/api/trusted-circle/${id}`), broadcastWeeklyReport: () => apiPost<{ success: boolean; sent_count: number; errors?: string[] }>("/api/weekly-report/broadcast", {}), analyzeAudio: async (audioBlob: Blob): Promise => { const formData = new FormData(); formData.append("audio", audioBlob, "recording.webm"); const headers: Record = {}; if (typeof window !== "undefined") { const token = localStorage.getItem("token"); if (token) headers["Authorization"] = `Bearer ${token}`; } const res = await fetch(`${API_URL}/api/analyze`, { method: "POST", headers, body: formData, }); if (!res.ok) throw new Error(`Analyze error ${res.status}`); return res.json(); }, setBaseline: async (audioBlob: Blob): Promise<{msg: string, has_baseline: boolean}> => { const formData = new FormData(); formData.append("audio", audioBlob, "calibration.webm"); const headers: Record = {}; if (typeof window !== "undefined") { const token = localStorage.getItem("token"); if (token) headers["Authorization"] = `Bearer ${token}`; } const res = await fetch(`${API_URL}/api/auth/baseline`, { method: "POST", headers, body: formData, }); if (!res.ok) throw new Error(`Baseline error ${res.status}`); return res.json(); } };