Spaces:
Sleeping
Sleeping
Aditey Kshirsagar
Refactor: update frontend to version 0.2.0, remove Tailwind CSS and PostCSS configurations, and add new Lamp component with dynamic physics simulation
451c559 | /* ββ 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<string, number>; | |
| explanation?: string; | |
| music_context?: Record<string, unknown>; | |
| targets_used?: Record<string, unknown> | 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<AnalysisResult> { | |
| 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<string, number> = {}; | |
| const kw: Record<string, string[]> = { | |
| 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, | |
| }; | |
| } | |