'use client'; import { motion, AnimatePresence } from 'framer-motion'; import { UploadCloud, FileAudio, CheckCircle, Loader2, FileAudio2 } from 'lucide-react'; import { useCallback, useState, useEffect, useRef } from 'react'; import { scrollToSection } from '@/config/navigation'; import { useAppStore } from '@/store/useAppStore'; import { apiService } from '@/services/api'; import { TiltCard } from '@/components/primitives/TiltCard'; import { InlineError } from '@/components/ui/InlineError'; import { Reveal } from '@/components/primitives/Reveal'; import { SectionHeader } from '@/components/ui/SectionHeader'; import { Waveform } from '@/components/animations/Waveform'; function WaveformCanvas() { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; let animationFrameId: number; let width = (canvas.width = canvas.offsetWidth); let height = (canvas.height = canvas.offsetHeight); const handleResize = () => { if (!canvas) return; width = canvas.width = canvas.offsetWidth; height = canvas.height = canvas.offsetHeight; }; window.addEventListener('resize', handleResize); const barWidth = 4; const gap = 3; const speed = 0.0015; const render = (time: number) => { ctx.clearRect(0, 0, width, height); // Create gradient for the waveform bars const gradient = ctx.createLinearGradient(0, 0, width, 0); gradient.addColorStop(0, '#3b82f6'); // blue gradient.addColorStop(0.5, '#60a5fa'); // light blue gradient.addColorStop(1, '#4f46e5'); // indigo ctx.fillStyle = gradient; const numBars = Math.ceil(width / (barWidth + gap)); const centerY = height / 2; for (let i = 0; i < numBars; i++) { // Calculate a multi-frequency sine wave height const x = i * (barWidth + gap); // Base sine wave const wave1 = Math.sin(i * 0.05 + time * speed) * 0.4; // Secondary harmonics to make it organic const wave2 = Math.sin(i * 0.12 - time * speed * 0.7) * 0.25; const wave3 = Math.cos(i * 0.03 + time * speed * 1.3) * 0.2; // Sum and scale the wave const combined = wave1 + wave2 + wave3; // Apply a center-hump envelope (bell curve) so the waves are taller in the center of the screen const distFromCenter = Math.abs(i - numBars / 2) / (numBars / 2); const envelope = Math.max(0, 1 - distFromCenter * distFromCenter); // parabolic envelope const barHeight = Math.max(4, (combined * 0.7 + 0.3) * (height * 0.4) * envelope); const y = centerY - barHeight / 2; ctx.beginPath(); if (ctx.roundRect) { ctx.roundRect(x, y, barWidth, barHeight, 2); } else { ctx.rect(x, y, barWidth, barHeight); } ctx.fill(); } animationFrameId = requestAnimationFrame(render); }; animationFrameId = requestAnimationFrame(render); return () => { window.removeEventListener('resize', handleResize); cancelAnimationFrame(animationFrameId); }; }, []); return ( ); } export function UploadSection() { const [isDragging, setIsDragging] = useState(false); const { isUploading, uploadProgress, audioFile, setUploading, setUploadProgress, setAudioFile, setTranscription, setExtracting, error, setError, } = useAppStore(); const handleFile = useCallback( async (file: File) => { if (!file || !file.type.startsWith('audio/')) { alert('Please upload an audio file'); return; } setError(null); setAudioFile(file); setUploading(true); setUploadProgress(0); const interval = setInterval(() => { const current = useAppStore.getState().uploadProgress; if (current >= 95) { clearInterval(interval); setUploadProgress(95); } else { setUploadProgress(current + 5); } }, 100); try { const response = await apiService.uploadAudio(file); clearInterval(interval); setUploadProgress(100); setUploading(false); setTranscription(response.transcription); setExtracting(true); useAppStore.getState().setFeatures(response.features); useAppStore.getState().setPrediction(null); useAppStore.getState().refreshFollowUpAlerts(); setExtracting(false); setTimeout(() => { scrollToSection('processing'); }, 800); } catch (err: unknown) { clearInterval(interval); setUploading(false); console.error(err); const message = err instanceof Error ? err.message : 'Backend transcription failed. Please ensure the API is running.'; setError(message); setTimeout(() => { scrollToSection('processing'); }, 800); } }, [ setAudioFile, setUploading, setUploadProgress, setTranscription, setExtracting, setError, ], ); const onDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }, []); const onDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); }, []); const onDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); const files = e.dataTransfer.files; if (files?.length) { handleFile(files[0]); } }, [handleFile], ); return (
{error && !error.includes('feature extraction') && !error.includes('Prediction failed') && ( setError(null)} /> )}
{!audioFile ? (

{isDragging ? 'Release to upload' : 'Drag and drop audio file'}

Supports MP3, WAV, or M4A. Mono or Stereo files.

) : (

{audioFile.name}

{(audioFile.size / (1024 * 1024)).toFixed(2)} MB ยท {audioFile.type || 'audio/*'}

{isUploading ? ( Transcribing ) : ( Done )}
{/* Connected Sound Waveform representation */}
Status {isUploading ? `Uploading file & running models (${uploadProgress}%)` : 'Processing successful'}
)}
); }