'use client'; import { useEffect, useRef } from 'react'; import { motion } from 'framer-motion'; import { ArrowRight, LineChart, ListChecks, Loader2, Zap } from 'lucide-react'; import { useAppStore } from '@/store/useAppStore'; import { apiService } from '@/services/api'; import { Button } from '@/components/ui/Button'; import { Card } from '@/components/ui/Card'; import { InlineError } from '@/components/ui/InlineError'; import { Reveal } from '@/components/ui/Reveal'; import { SectionHeader } from '@/components/ui/SectionHeader'; import { cn } from '@/lib/utils'; function DataStreamCanvas() { 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 streams: { x: number; y: number; speed: number; length: number; opacity: number }[] = []; const points: { x: number; y: number; vy: number; radius: number; opacity: number }[] = []; // Initialize vertical data stream lines const streamCount = Math.floor(width / 24); for (let i = 0; i < streamCount; i++) { streams.push({ x: Math.random() * width, y: Math.random() * height, speed: Math.random() * 1.5 + 0.5, length: Math.random() * 80 + 40, opacity: Math.random() * 0.15 + 0.05, }); } // Initialize floating data points const pointCount = 25; for (let i = 0; i < pointCount; i++) { points.push({ x: Math.random() * width, y: Math.random() * height, vy: -(Math.random() * 0.6 + 0.2), radius: Math.random() * 2 + 1, opacity: Math.random() * 0.35 + 0.1, }); } const draw = () => { ctx.clearRect(0, 0, width, height); // Draw flowing vertical lines (data streams) for (const s of streams) { s.y += s.speed; if (s.y - s.length > height) { s.y = -s.length; s.x = Math.random() * width; } const lineGrad = ctx.createLinearGradient(s.x, s.y - s.length, s.x, s.y); lineGrad.addColorStop(0, 'rgba(59, 130, 246, 0)'); lineGrad.addColorStop(1, `rgba(59, 130, 246, ${s.opacity})`); ctx.strokeStyle = lineGrad; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(s.x, s.y - s.length); ctx.lineTo(s.x, s.y); ctx.stroke(); } // Draw floating data points for (const p of points) { p.y += p.vy; if (p.y < 0) { p.y = height; p.x = Math.random() * width; } ctx.fillStyle = `rgba(59, 130, 246, ${p.opacity})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); ctx.fill(); if (p.radius > 2) { ctx.strokeStyle = `rgba(96, 165, 250, ${p.opacity * 0.5})`; ctx.lineWidth = 0.5; ctx.beginPath(); ctx.arc(p.x, p.y, p.radius * 2.5, 0, Math.PI * 2); ctx.stroke(); } } animationFrameId = requestAnimationFrame(draw); }; animationFrameId = requestAnimationFrame(draw); return () => { window.removeEventListener('resize', handleResize); cancelAnimationFrame(animationFrameId); }; }, []); return ; } export function PredictionSection() { const { isPredicting, prediction, features, setPredicting, error, setError } = useAppStore(); const handlePredict = async () => { if (!features) return; setPredicting(true); setError(null); try { const result = await apiService.predictConversion(features); useAppStore.getState().setPrediction(result); setPredicting(false); } catch (err: unknown) { console.error(err); setPredicting(false); const message = err instanceof Error ? err.message : 'Prediction failed. Check that XGBoost API is running.'; setError(message); } }; const showError = error?.includes('Prediction failed'); const probabilityLabel = prediction && prediction.probability >= 0.7 ? 'High conversion likelihood' : prediction && prediction.probability >= 0.4 ? 'Moderate conversion likelihood' : 'Low conversion likelihood'; const riskClass = prediction?.risk === 'Low' ? 'text-emerald-600 border-emerald-500/25 bg-emerald-500/10' : prediction?.risk === 'Medium' ? 'text-amber-600 border-amber-500/25 bg-amber-500/10' : 'text-red-500 border-red-500/25 bg-red-500/10'; return (
{showError && ( setError(null)} /> )} {isPredicting ? (

Running conversion model…

Scoring extracted features

) : prediction ? (
{Math.round(prediction.probability * 100)} % Probability

{probabilityLabel}

{prediction.risk} risk

Model reasoning

    {prediction.insights.map((insight, i) => (
  • {i + 1} {insight}
  • ))}
{prediction.nextSteps && prediction.nextSteps.length > 0 && (

Recommended actions

    {prediction.nextSteps.map((step, i) => (
  • {i + 1} {step}
  • ))}
)}
) : ( {/* Empty state: full-section CSS animated background */}
{/* Animated data streams canvas */}

Conversion Scoring Model

{features ? 'Run the XGBoost classifier model to calculate probability scores and actionable follow-up advice.' : 'Upload an audio file and run feature extraction to unlock predictive conversion scoring.'}

{features && ( )}
)}
); }