Spaces:
Running
Running
| // 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<string, string> { | |
| if (typeof window === "undefined") return {}; | |
| const token = localStorage.getItem("token"); | |
| return token ? { Authorization: `Bearer ${token}` } : {}; | |
| } | |
| export async function apiGet<T>(path: string): Promise<T> { | |
| 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<T>(path: string, body: unknown): Promise<T> { | |
| 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<T>(path: string, body?: unknown): Promise<T> { | |
| 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<T>(path: string): Promise<T> { | |
| 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<string, number | null>; | |
| last_week: Record<string, number | null>; | |
| 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<string, unknown>; | |
| 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<VoiceEntry[]>(`/api/entries?days=${days}`), | |
| getTrends: () => | |
| apiGet<TrendsData>(`/api/trends`), | |
| getAlerts: () => | |
| apiGet<MoodAlert[]>(`/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<Array<{ id: string; role: string; content: string; created_at: string }>>( | |
| `/api/chat/history?channel_id=${channelId}` | |
| ), | |
| getChannels: () => | |
| apiGet<ChatChannel[]>("/api/chat/channels"), | |
| createChannel: (title = "New Chat") => | |
| apiPost<ChatChannel>("/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<Record<string, unknown>>("/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<Array<{ date: string; sleep_hours: number; mood_score: number; energy_score: number; emotion: string }>>("/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<AnalyzeResult> => { | |
| const formData = new FormData(); | |
| formData.append("audio", audioBlob, "recording.webm"); | |
| const headers: Record<string, string> = {}; | |
| 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<string, string> = {}; | |
| 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(); | |
| } | |
| }; | |