'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 Chip from '@/components/ui/Chip'; 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, selections, isAnalyzingSite, agentAmplitude, startConversation, endConversation, completedBrief, completedFormData, } = useVoiceAgent(); const transcriptEndRef = useRef(null); // Auto-scroll transcript within its container only useEffect(() => { const el = transcriptEndRef.current; if (el?.parentElement) { el.parentElement.scrollTop = el.parentElement.scrollHeight; } }, [transcript]); // Handle completion useEffect(() => { if (completedBrief && completedFormData) { const timer = setTimeout(() => { onComplete(completedBrief, completedFormData); }, 1500); return () => clearTimeout(timer); } }, [completedBrief, completedFormData, onComplete]); // 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)'], ); // Build selection chips const chipLabels: string[] = []; if (selections.services) { for (const svc of selections.services) { try { chipLabels.push(t(`services.${svc}.title`)); } catch { chipLabels.push(svc); } } } if (selections.aiEnabled && selections.aiTypes) { for (const ai of selections.aiTypes) { try { chipLabels.push(t(`aiTypes.${ai}.title`)); } catch { chipLabels.push(ai); } } } if (selections.industry) { try { chipLabels.push(t(`industries.${selections.industry}`)); } catch { chipLabels.push(selections.industry); } } if (selections.timeline) { try { chipLabels.push(t(`timelines.${selections.timeline}`)); } catch { chipLabels.push(selections.timeline); } } return (
{/* Agent card header */}
L

{t('voice.agentName')}

{status === 'active' ? 'Connected' : status === 'connecting' ? t('voice.connecting') : 'Ready'}
{/* Waveform orb */}
{status === 'idle' && ( )} {status === 'connecting' && ( )} {status === 'active' && ( )} {/* Analyzing site badge */} {isAnalyzingSite && ( {t('voice.analyzingSite')} )} {/* Error message */} {errorMessage && (

{errorMessage}

)}
{/* Live transcript */} {transcript.length > 0 && (
{transcript.map((entry, i) => ( ))}
)} {/* Selection chips */} {chipLabels.length > 0 && (

{t('voice.capturedSoFar')}

{chipLabels.map((label, i) => ( {label} ))}
)}
{/* Controls */}
{status === 'idle' && ( )} {status === 'active' && ( <> )} {status === 'connecting' && (

{t('voice.connecting')}

)}
); }