/** * HuggingFace Spaces API Client * * Calls the ML API hosted on HuggingFace Spaces instead of local Python. * This enables deployment on platforms without Python support. * * Set HF_SPACES_URL environment variable to your Space URL: * HF_SPACES_URL=https://username-space-name.hf.space */ import FormData from 'form-data'; import fs from 'fs'; import path from 'path'; const HF_SPACES_URL = process.env.HF_SPACES_URL; export function isHfSpacesEnabled(): boolean { return !!HF_SPACES_URL; } export function getSpacesUrl(): string | undefined { return HF_SPACES_URL; } interface EmbeddingResult { success: boolean; embedding?: number[]; dimension?: number; error?: string; } interface ChunkResult { success: boolean; total_duration?: number; chunk_count?: number; chunks?: Array<{ start_time: number; end_time: number; embedding: number[]; }>; error?: string; } interface SearchResult { matches: Array<{ score: number; trackId: string; title: string; artist: string; distinctiveness?: number; }>; mean_similarity?: number; } /** * Extract MERT embedding via HuggingFace Spaces API */ export async function extractEmbedding(audioPath: string): Promise { if (!HF_SPACES_URL) { return { success: false, error: "HF_SPACES_URL not configured" }; } try { const formData = new FormData(); formData.append('audio_file', fs.createReadStream(audioPath)); const response = await fetch(`${HF_SPACES_URL}/api/extract_embedding`, { method: 'POST', body: formData as any, }); const text = await response.text(); return JSON.parse(text); } catch (error) { return { success: false, error: String(error) }; } } /** * Extract chunk embeddings via HuggingFace Spaces API */ export async function extractChunks( audioPath: string, chunkDuration: number = 10.0, overlap: number = 5.0 ): Promise { if (!HF_SPACES_URL) { return { success: false, error: "HF_SPACES_URL not configured" }; } try { const formData = new FormData(); formData.append('audio_file', fs.createReadStream(audioPath)); formData.append('chunk_duration', String(chunkDuration)); formData.append('overlap', String(overlap)); const response = await fetch(`${HF_SPACES_URL}/api/extract_chunks`, { method: 'POST', body: formData as any, }); const text = await response.text(); return JSON.parse(text); } catch (error) { return { success: false, error: String(error) }; } } /** * Search for similar tracks via HuggingFace Spaces API */ export async function searchSimilar( queryEmbedding: number[], indexData: { entries: Array<{ trackId: string; title: string; artist: string; embedding: number[] }> }, k: number = 5 ): Promise { if (!HF_SPACES_URL) { return { matches: [] }; } try { const formData = new FormData(); formData.append('query_embedding_json', JSON.stringify(queryEmbedding)); formData.append('index_json', JSON.stringify(indexData)); formData.append('k', String(k)); const response = await fetch(`${HF_SPACES_URL}/api/search`, { method: 'POST', body: formData as any, }); const text = await response.text(); return JSON.parse(text); } catch (error) { return { matches: [] }; } } /** * Check if HuggingFace Spaces API is healthy */ export async function checkHealth(): Promise<{ ok: boolean; details?: any }> { if (!HF_SPACES_URL) { return { ok: false, details: { error: "HF_SPACES_URL not configured" } }; } try { const response = await fetch(`${HF_SPACES_URL}/api/health`, { method: 'POST', }); const text = await response.text(); const data = JSON.parse(text); return { ok: data.status === 'ok', details: data }; } catch (error) { return { ok: false, details: { error: String(error) } }; } }