'use client'; import { useEffect, useRef } from 'react'; import { useTranslations } from 'next-intl'; import { motion, AnimatePresence, useMotionValue, useTransform } from 'framer-motion'; import { Mic, MicOff, PhoneOff, Loader2 } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useVoiceAgent, type TranscriptEntry } from './VoiceAgentProvider'; import type { WizardFormData } from './WizardContainer'; // ─── Types ─────────────────────────────────────────────────────────────────── interface VoiceAgentProps { locale: string; onComplete: (brief: string, formData: WizardFormData) => void; } // ─── Transcript Bubble ─────────────────────────────────────────────────────── function TranscriptBubble({ entry }: { entry: TranscriptEntry }) { return (
{entry.text}
); } // ─── Main Component ────────────────────────────────────────────────────────── export default function VoiceAgent({ locale, onComplete }: VoiceAgentProps) { const t = useTranslations('configurator'); const { status, errorMessage, isMicActive, toggleMic, transcript, isAnalyzingSite, isGeneratingBrief, agentAmplitude, startConversation, endConversation, completedBrief, completedFormData, pendingContact, confirmContact, updatePendingContact, canReconnect, reconnect, } = useVoiceAgent(); const transcriptEndRef = useRef(null); // Auto-scroll transcript useEffect(() => { transcriptEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }); }, [transcript]); // Handle completion — end the call, then transition useEffect(() => { if (completedBrief && completedFormData) { console.log('[VoiceAgent] Brief complete, ending conversation and transitioning in 1.5s...'); endConversation(); const timer = setTimeout(() => { console.log('[VoiceAgent] Calling onComplete'); onComplete(completedBrief, completedFormData); }, 1500); return () => clearTimeout(timer); } }, [completedBrief, completedFormData, onComplete, endConversation]); // Orb animation driven by agent amplitude const amplitudeValue = useMotionValue(0); useEffect(() => { amplitudeValue.set(agentAmplitude); }, [agentAmplitude, amplitudeValue]); const orbScale = useTransform(amplitudeValue, [0, 0.5], [1, 1.18]); const orbGlow = useTransform( amplitudeValue, [0, 0.5], ['0px 0px 0px rgba(0,100,148,0)', '0px 0px 30px rgba(0,100,148,0.3)'], ); return (
{/* Agent card header */}
L

{t('voice.agentName')}

{status === 'active' ? 'Connected' : status === 'connecting' ? t('voice.connecting') : 'Ready'}
{/* Waveform orb */}
{status === 'idle' && !isGeneratingBrief && ( )} {(status === 'connecting' || (status === 'idle' && isGeneratingBrief)) && ( )} {status === 'active' && ( )} {/* Status badges */} {isAnalyzingSite && ( {t('voice.analyzingSite')} )} {isGeneratingBrief && !completedBrief && ( {t('voice.generatingBrief')} )} {/* Error message */} {errorMessage && !canReconnect && (

{errorMessage}

)}
{/* Live transcript */} {transcript.length > 0 && (
{transcript.map((entry, i) => ( ))}
)} {/* Contact confirmation card */} {pendingContact && !completedBrief && (

{t('voice.contactConfirm')}

updatePendingContact('name', e.target.value)} className="flex-1 text-sm text-on-surface bg-white rounded-lg border border-outline-variant/30 px-3 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary/40" />
updatePendingContact('email', e.target.value)} className="flex-1 text-sm text-on-surface bg-white rounded-lg border border-outline-variant/30 px-3 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary/40" />
)}
{/* Controls — sticky on mobile for thumb reach */}
{status === 'idle' && !completedBrief && !isGeneratingBrief && ( )} {status === 'active' && ( <> )} {status === 'connecting' && (

{t('voice.connecting')}

)} {(status === 'error' || canReconnect) && !completedBrief && (

{t('voice.connectionLost')}

)}
); }