"use client"; import { useState, useEffect, useCallback, Suspense, useRef } from "react"; import { useUser, useClerk } from "@clerk/nextjs"; import { useSearchParams } from "next/navigation"; import { UploadCloud, Music, AudioLines, Settings2, ShieldCheck, Zap, Lock, Sliders, Activity, Mic2, Search, LogOut, History, Copy, Check, FileMusic, AlignLeft, Users, CheckCircle2, XCircle, List, Plus } from 'lucide-react'; import { db } from "../lib/firebase"; import { collection, addDoc, getDocs, query, where, deleteDoc, doc, serverTimestamp, orderBy } from "firebase/firestore"; import ExtractionFlowDiagram from "../components/ExtractionFlowDiagram"; import SmartCompareStudio from "../components/SmartCompareStudio"; import QueueWaitingRoom from "../components/QueueWaitingRoom"; import SyncedTranscriptPlayer from "../components/SyncedTranscriptPlayer"; import RawTranscriptViewer from "../components/RawTranscriptViewer"; import FAQAccordion from "../components/FAQAccordion"; import { faqs } from "../data/faqs"; import Link from "next/link"; function HomeContent() { const [file, setFile] = useState(null); const [loading, setLoading] = useState(false); const [progress, setProgress] = useState({ step: "", percent: 0, message: "", chunks_total: 0, chunks_completed: 0, chunks_pending: 0, start_time: 0, eta_seconds: 0, completed_time: 0, queue_position: 0 }); const [resultZip, setResultZip] = useState(null); const [taskId, setTaskId] = useState(null); const [error, setError] = useState(null); const vocalsAudioRef = useRef(null); const { user, isLoaded, isSignedIn } = useUser(); const { openSignIn } = useClerk(); const searchParams = useSearchParams(); const [userEmail, setUserEmail] = useState(null); const [loginInput, setLoginInput] = useState(""); const [searchTaskId, setSearchTaskId] = useState(""); const baseUrl = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8000"; const eventSourceRef = useRef(null); const pollProgress = useCallback((id: string) => { if (eventSourceRef.current) { eventSourceRef.current.close(); } const eventSource = new EventSource(`${baseUrl}/api/events/status/${id}`); eventSourceRef.current = eventSource; eventSource.onmessage = (event) => { const data = JSON.parse(event.data); if (data.error) { eventSource.close(); setError(data.error === "Task not found" ? "This task is no longer available. The server restarts periodically to free up resources, which clears old files. Please upload your audio again." : data.error); setLoading(false); setTaskId(null); window.history.replaceState({}, '', '/'); return; } if (data.status === "processing" || data.status === "pending" || data.status === "queued") { setProgress({ step: data.step || (data.status === "queued" ? "Queued" : "Processing..."), percent: data.progress || 5, message: data.message || "Working...", chunks_total: data.chunks_total || 0, chunks_completed: data.chunks_completed || 0, chunks_pending: data.chunks_pending || 0, start_time: data.start_time || 0, eta_seconds: data.eta_seconds || 0, completed_time: data.completed_time || 0, queue_position: data.queue_position || 0, status: data.status } as any); } else if (data.status === "completed") { eventSource.close(); setResultZip(data.result_path || true); setLoading(false); setProgress({ step: "Complete", percent: 100, message: "Ready to download!", chunks_total: 0, chunks_completed: 0, chunks_pending: 0, start_time: 0, eta_seconds: 0, completed_time: 0, queue_position: 0 }); } else if (data.status === "failed") { eventSource.close(); setError(data.message || data.error || "An unknown error occurred on the server."); setLoading(false); } else if (data.status === "cancelled" || data.status === "expired") { eventSource.close(); setError(data.status === "cancelled" ? "Processing was cancelled." : "This task is no longer available. The server restarts periodically to free up resources, which clears old files. Please upload your audio again."); setLoading(false); setTaskId(null); window.history.replaceState({}, '', '/'); } }; eventSource.onerror = () => { // SSE auto-reconnects on network drops. // If the backend strictly sends data.error on 404, we handle it above. }; }, [baseUrl]); // Clean up SSE on component unmount useEffect(() => { return () => { if (eventSourceRef.current) { eventSourceRef.current.close(); } }; }, []); useEffect(() => { const queryTaskId = searchParams.get('taskId'); if (queryTaskId && !taskId && !loading && !resultZip) { setTimeout(() => { setTaskId(queryTaskId); setLoading(true); setProgress({ step: "Looking up task...", percent: 10, message: "Connecting to server...", chunks_total: 0, chunks_completed: 0, chunks_pending: 0, start_time: 0, eta_seconds: 0, completed_time: 0, queue_position: 0 }); pollProgress(queryTaskId); }, 0); } }, [searchParams, taskId, loading, resultZip, pollProgress]); useEffect(() => { const savedEmail = localStorage.getItem("user_email"); if (savedEmail) { setTimeout(() => setUserEmail(savedEmail), 0); } }, []); const selectTask = (task: any) => { setTaskId(task.task_id); if (task.status === "completed") { if (eventSourceRef.current) eventSourceRef.current.close(); setResultZip(task.result_path || true); setLoading(false); setProgress({ step: "Complete", percent: 100, message: "Ready to download!", chunks_total: 0, chunks_completed: 0, chunks_pending: 0, start_time: 0, eta_seconds: 0, completed_time: 0, queue_position: 0 } as any); setError(null); } else if (task.status === "failed" || task.status === "cancelled") { if (eventSourceRef.current) eventSourceRef.current.close(); setError(task.message || "Task failed or cancelled."); setResultZip(null); setLoading(false); } else { setResultZip(null); setError(null); setLoading(true); pollProgress(task.task_id); } }; const startNewUpload = () => { if (eventSourceRef.current) eventSourceRef.current.close(); setFile(null); setTaskId(null); setResultZip(null); setError(null); setLoading(false); setProgress({ step: "", percent: 0, message: "", chunks_total: 0, chunks_completed: 0, chunks_pending: 0, start_time: 0, eta_seconds: 0, completed_time: 0, queue_position: 0 } as any); window.history.replaceState({}, '', '/'); }; // Feature Toggles const [isolateVocals, setIsolateVocals] = useState(false); const [enhance, setEnhance] = useState(false); const [lyricSync, setLyricSync] = useState(false); // Download Customization Options const [dlFormat, setDlFormat] = useState("mp3"); const [dlChunked, setDlChunked] = useState(true); const [dlFolderName, setDlFolderName] = useState("My_Song_Stems"); const [dlStems, setDlStems] = useState>({ vocals: true }); const handleFileChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files[0]) { setFile(e.target.files[0]); } }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); if (e.dataTransfer.files && e.dataTransfer.files[0]) { setFile(e.dataTransfer.files[0]); } }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); }; const handleUpload = async () => { if (!file) return; if (!isSignedIn) { openSignIn(); return; } setLoading(true); setError(null); setResultZip(null); setProgress({ step: "Uploading...", percent: 5, message: "Sending file to cloud...", chunks_total: 0, chunks_completed: 0, chunks_pending: 0, start_time: 0, eta_seconds: 0, completed_time: 0, queue_position: 0 }); const formData = new FormData(); formData.append("file", file); formData.append("isolate_vocals", isolateVocals.toString()); formData.append("enhance_speech", enhance.toString()); formData.append("lyric_sync", lyricSync.toString()); formData.append("email", user?.primaryEmailAddress?.emailAddress || ""); try { const data = await new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("POST", `${baseUrl}/api/process`, true); // Track real-time upload progress xhr.upload.onprogress = (event) => { if (event.lengthComputable) { const uploadPercent = Math.round((event.loaded / event.total) * 100); // Cap the UI progress bar at 10% during upload phase const uiPercent = Math.max(1, Math.min(10, Math.floor(uploadPercent / 10))); setProgress({ step: "Uploading...", percent: uiPercent, message: `Sending file to cloud... ${uploadPercent}%`, chunks_total: 0, chunks_completed: 0, chunks_pending: 0, start_time: 0, eta_seconds: 0, completed_time: 0, queue_position: 0 }); } }; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { try { resolve(JSON.parse(xhr.responseText)); } catch (e) { reject(new Error("Invalid response from server")); } } else { // Check if it's a payload too large error (HTTP 413) if (xhr.status === 413) { reject(new Error("File is too large! The Hugging Face free tier cannot accept files this big.")); } else { reject(new Error("Failed to process file")); } } }; xhr.onerror = () => reject(new Error("Network error occurred during upload. Check your internet connection.")); xhr.send(formData); }); setTaskId(data.task_id); // Save to Firebase History if (user?.primaryEmailAddress?.emailAddress) { try { await addDoc(collection(db, "extractions"), { taskId: data.task_id, email: user.primaryEmailAddress.emailAddress, filename: file.name, createdAt: serverTimestamp(), options: { isolateVocals, enhance, lyricSync } }); } catch (fbErr) { console.error("Failed to save to Firebase history:", fbErr); } } pollProgress(data.task_id); } catch (err: any) { setError(err.message || "An error occurred"); setLoading(false); } }; // pollProgress moved to top to satisfy ESLint return (
{/* Massive Hero Section */}

Extract Vocals & Stems instantly.

The professional AI vocal remover and music source separation engine.

{/* Main Split-Screen Dashboard UI */}
{/* Subtle global background glow behind the entire dashboard */}
{/* LEFT SIDEBAR: TOOLS PANEL */}

Extraction Tools

{/* Basic Extraction Tools */}

{/* Pro / Coming Soon Tools */} {/* Pro / Coming Soon Tools */}

Advanced / Pro Features

Select your tools before dropping a file to process.

{/* RIGHT SIDE: FILE UPLOAD & RESULTS */}
{/* Dynamic Content Area */}
{/* Massive Dropzone */} {!resultZip && !loading && (
document.getElementById("fileInput")?.click()} > {file ? (

{file.name}

) : ( <> {!isLoaded ? (

Loading...

) : !isSignedIn ? ( <>

Login Required

You must sign in via the top right to process files.

) : ( <>

Select Files

or drag and drop them here

Supports MP3, WAV, FLAC, MP4, and more.

)} )}
)} {/* Error Message */} {error &&
{error}
} {/* Loading State */} {loading && (progress as any).status !== "queued" && (
{/* Status Content */}

{progress.percent}%

{progress.step || "Preparing audio..."}

{progress.message}

{/* TIMING METRICS */}
Started: {progress.start_time ? new Date(progress.start_time * 1000).toLocaleTimeString() : '...'}
{progress.eta_seconds > 0 && progress.eta_seconds < 3600 && (
ETA: {progress.eta_seconds}s
)} {progress.eta_seconds >= 3600 && (
ETA: {Math.floor(progress.eta_seconds / 60)}m
)}
{/* CHUNK MATRIX */} {progress.chunks_total > 0 && (
Live Chunk Matrix
{[...Array(progress.chunks_total)].map((_, i) => { const isCompleted = i < progress.chunks_completed; const isProcessing = !isCompleted && i < progress.chunks_completed + 5; // Represents max 5 parallel cloud workers return (
{i + 1}
); })}
)}
)} {/* Audio Player and Download (Success State) */} {resultZip && taskId && (

Extraction Complete!

Your stems have been successfully separated.

Task ID: {taskId}
{lyricSync && } {lyricSync && }

Download Customization

setDlFolderName(e.target.value)} placeholder="My_Song_Stems" className="w-full bg-[#1a1a1a] text-white border border-[#27272a] rounded-lg px-4 py-3 outline-none focus:border-[#1877F2]" />
)} {/* MAIN FLOW DIAGRAM OR QUEUE */} {(progress as any).status === "queued" ? ( { if (!taskId) return; try { await fetch(`${baseUrl}/api/cancel/${taskId}`, { method: 'POST' }); setLoading(false); setResultZip(null); setTaskId(null); setFile(null); window.history.replaceState({}, '', '/'); setError("Processing was cancelled from the queue."); } catch (err) { console.error(err); } }} /> ) : ( )}
{/* Trust / Features Section */}

Fast Processing

Our next-gen AI splits songs in less than a minute. No waiting in queues for hours.

Studio Quality

Extract completely clean vocals and stems with zero artifacting using Demucs v4.

100% Secure

Your files are encrypted during upload and permanently deleted after processing.

{/* FAQ Section on Homepage */}

Frequently Asked Questions

Quick answers to common questions about VocalBee.

View all 100 FAQs →
); } export default function Home() { return ( Loading...
}> ); }