/* ── EUMORA API client ────────────────────────────────────────────────────── */ import { EMOTIONS, BACKEND_EMOTION_MAP, type EmotionPreset } from "./data"; const API_URL = process.env.NEXT_PUBLIC_API_URL?.replace(/\/$/, "") || "http://localhost:8000"; // ── Types ──────────────────────────────────────────────────────────────────── export interface BackendTrack { name: string; artist: string; album: string; album_art?: string; spotify_url?: string; preview_url?: string | null; popularity: number; match_score: number; } export interface BackendResult { emotion: string; confidence: number; probabilities: Record; explanation?: string; music_context?: Record; targets_used?: Record | null; tracks: BackendTrack[]; spotify_unavailable?: boolean; } /** The shape consumed by the New Frontend UI */ export interface AnalysisResult { emotion: string; // key in EMOTIONS (melancholy, euphoric, etc.) confidence: number; // 0–1 summary: string; // poetic descriptor tracks: DisplayTrack[]; // either from Spotify or curated fallback fromBackend: boolean; // true if real backend was used } export interface DisplayTrack { title: string; artist: string; album: string; year: number | string; duration: string; album_art?: string; spotify_url?: string; match_score?: number; } // ── API call ───────────────────────────────────────────────────────────────── /** * Call the backend /api/recommend endpoint and translate the response into * the format expected by the New Frontend UI. * * Falls back to curated presets if: * - Backend is unreachable * - Spotify is unavailable on the backend */ export async function analyze(text: string): Promise { try { const res = await fetch(`${API_URL}/api/recommend`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text, limit: 10 }), }); if (!res.ok) { throw new Error(`Backend returned ${res.status}`); } const data: BackendResult = await res.json(); // Map backend emotion → frontend emotion key const frontendEmotion = BACKEND_EMOTION_MAP[data.emotion] ?? "melancholy"; const emo: EmotionPreset = EMOTIONS[frontendEmotion] ?? EMOTIONS.melancholy; // If Spotify returned real tracks, use them if (data.tracks && data.tracks.length > 0 && !data.spotify_unavailable) { const displayTracks: DisplayTrack[] = data.tracks.map((t) => ({ title: t.name, artist: t.artist, album: t.album, year: "", duration: "", album_art: t.album_art, spotify_url: t.spotify_url, match_score: t.match_score, })); return { emotion: frontendEmotion, confidence: data.confidence, summary: data.explanation || emo.descriptor, tracks: displayTracks, fromBackend: true, }; } // Spotify unavailable — use curated fallback tracks return { emotion: frontendEmotion, confidence: data.confidence, summary: data.explanation || emo.descriptor, tracks: emo.tracks.map((t) => ({ ...t, year: t.year })), fromBackend: true, }; } catch { // Backend completely unreachable — do client-side fallback return analyzeClientFallback(text); } } // ── Client-side fallback ───────────────────────────────────────────────────── function analyzeClientFallback(text: string): AnalysisResult { const t = text.toLowerCase(); const score: Record = {}; const kw: Record = { melancholy: ["sad", "heavy", "blue", "rain", "alone", "empty", "tired", "ache"], euphoric: ["smiling", "alive", "happy", "joy", "ecstatic", "high", "best", "summer", "sun"], anxious: ["panic", "spiral", "anxious", "racing", "can't", "3am", "awake", "stress"], lonely: ["lonely", "miss", "without", "her name", "his name", "empty room", "alone"], nostalgic: ["remember", "back when", "used to", "old", "childhood", "summer of", "missed"], yearning: ["want", "need you", "long for", "wish", "ache", "almost", "across"], peaceful: ["calm", "quiet", "still", "soft", "warm", "easy", "settled"], defiant: ["hate", "no more", "done", "fight", "scream", "burn", "angry"], }; Object.entries(kw).forEach(([emo, words]) => { score[emo] = words.reduce((s, w) => s + (t.includes(w) ? 1 : 0), 0); }); const sorted = Object.entries(score).sort((a, b) => b[1] - a[1]); const top = sorted[0][1] > 0 ? sorted[0][0] : "melancholy"; const confidence = sorted[0][1] > 0 ? Math.min(0.95, 0.6 + sorted[0][1] * 0.08) : 0.52; const emo = EMOTIONS[top] ?? EMOTIONS.melancholy; return { emotion: top, confidence, summary: emo.descriptor, tracks: emo.tracks.map((tr) => ({ ...tr, year: tr.year })), fromBackend: false, }; }