"use client"; import { useState, useEffect, useRef } from "react"; type Language = "es" | "en"; const TTS_PROXY = "/api/tts-proxy"; const labels = { es: { title: "Cleopatra", subtitle: "Asistente de Ventas IA Premium", status: { listening: "🎤 Escuchando...", speaking: "🔊 Hablando...", thinking: "⏳ Pensando...", ready: "💬 Lista para ayudarte" }, youSaid: "Dijiste:", cleopatra: "👑 Cleopatra:", micHint: "Toca el micrófono", speakingHint: "Habla ahora...", tryAgain: "Tengo problemas para conectar.", placeholder: "Escribe aquí..." }, en: { title: "Cleopatra", subtitle: "Premium AI Sales Assistant", status: { listening: "🎤 Listening...", speaking: "🔊 Speaking...", thinking: "⏳ Thinking...", ready: "💬 Ready to help" }, youSaid: "You said:", cleopatra: "👑 Cleopatra:", micHint: "Tap the microphone", speakingHint: "Speak now...", tryAgain: "I'm having trouble connecting.", placeholder: "Type here..." } }; export default function CleopatraVoiceWidget() { const [lang, setLang] = useState("es"); const [isListening, setIsListening] = useState(false); const [isSpeaking, setIsSpeaking] = useState(false); const [isThinking, setIsThinking] = useState(false); const [transcript, setTranscript] = useState(""); const [lastReply, setLastReply] = useState(""); const [inputText, setInputText] = useState(""); const recognitionRef = useRef(null); const audioRef = useRef(null); const streamRef = useRef(null); const t = labels[lang]; useEffect(() => { return () => { stopAll(); }; }, []); const stopAll = () => { if (streamRef.current) { streamRef.current.getTracks().forEach(track => track.stop()); streamRef.current = null; } if (recognitionRef.current) { recognitionRef.current.abort(); recognitionRef.current = null; } setIsListening(false); if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } setIsSpeaking(false); }; const speak = async (text: string) => { try { setIsSpeaking(true); // Use proxy to fetch TTS (handles HTTP->HTTPS) const res = await fetch(TTS_PROXY, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text, lang }) }); if (!res.ok) throw new Error("TTS failed"); const data = await res.json(); const base64Audio = data.audio; if (!base64Audio) throw new Error("No audio"); // Play audio const audio = new Audio(`data:audio/mp3;base64,${base64Audio}`); audioRef.current = audio; audio.onended = () => setIsSpeaking(false); audio.onerror = () => { setIsSpeaking(false); console.error("Audio playback error"); }; await audio.play(); } catch (err) { console.error("TTS error:", err); // Fallback to browser TTS const utterance = new SpeechSynthesisUtterance(text); utterance.lang = lang === "es" ? "es-ES" : "en-US"; utterance.rate = 0.9; utterance.onstart = () => setIsSpeaking(true); utterance.onend = () => setIsSpeaking(false); utterance.onerror = () => setIsSpeaking(false); window.speechSynthesis.speak(utterance); } }; const toggleLang = () => { setLang(prev => prev === "es" ? "en" : "es"); }; const startListening = async () => { try { stopAll(); const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } }); streamRef.current = stream; const SpeechRecognition = (window as any).webkitSpeechRecognition || (window as any).SpeechRecognition; if (!SpeechRecognition) { alert(lang === "es" ? "Speech recognition not supported" : "Speech recognition not supported"); return; } const recognition = new SpeechRecognition(); recognition.continuous = false; recognition.interimResults = true; recognition.lang = lang === "es" ? "es-ES" : "en-US"; recognitionRef.current = recognition; recognition.onstart = () => { setIsListening(true); setTranscript(""); }; recognition.onresult = (event: any) => { const result = event.results[0]; const text = result[0].transcript; setTranscript(text); if (result.isFinal) { handleSend(text); setIsListening(false); } }; recognition.onend = () => setIsListening(false); recognition.onerror = (event: any) => { console.error("Speech error:", event.error); setIsListening(false); if (event.error !== "no-speech") { alert(lang === "es" ? "Error de voz: " + event.error : "Speech error: " + event.error); } }; recognition.start(); } catch (err) { console.error("Mic error:", err); alert(lang === "es" ? "No se pudo acceder al micrófono" : "Could not access microphone"); } }; const stopListening = () => { if (recognitionRef.current) recognitionRef.current.abort(); setIsListening(false); }; const handleSend = async (text: string) => { if (!text.trim()) return; setIsThinking(true); setLastReply(""); try { const res = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text, language: lang }) }); const data = await res.json(); const reply = data.response || (lang === "es" ? "Estoy aquí para ayudarte." : "I'm here to help."); setLastReply(reply); await speak(reply); } catch (err) { console.error("Error:", err); const errorReply = t.tryAgain; setLastReply(errorReply); await speak(errorReply); } setIsThinking(false); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (inputText.trim()) { handleSend(inputText); setInputText(""); } }; const skipSpeaking = () => { if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } setIsThinking(false); setIsSpeaking(false); }; return (
{/* Language Toggle */}
{/* Header */}
👑

{t.title}

{t.subtitle}

{/* Status */}
{isListening ? t.status.listening : isSpeaking ? t.status.speaking : isThinking ? t.status.thinking : t.status.ready}
{/* Visualizer */}
{[...Array(16)].map((_, i) => (
))}
{/* Transcript */} {transcript && (

{t.youSaid}

{transcript}

)} {/* Reply */} {lastReply && (

{t.cleopatra}

{lastReply}

)} {/* Text Input */}
setInputText(e.target.value)} placeholder={t.placeholder} className="flex-1 bg-black/40 border border-purple-500/30 rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:border-purple-500" />
{/* Controls */}
{(isSpeaking || isThinking) && ( )}
{/* Hint */}

{isListening ? t.speakingHint : t.micHint}

); }