Files
sitemente/app/components/VoiceWidget.tsx
T
horus d5575b58e3 SiteMente - AI-Powered Lead Generation Platform
Features:
- Mission Control dashboard
- HP Submissions tracking
- AI Agents integration
- Lead management CRM
- Marketing email templates
- Chrome extension support

Tech: Next.js, TypeScript, Tailwind CSS, MySQL
2026-03-19 17:38:12 +01:00

188 lines
5.9 KiB
TypeScript

"use client";
import { useState, useEffect, useRef } from "react";
export default function VoiceWidget() {
const [isListening, setIsListening] = useState(false);
const [transcript, setTranscript] = useState("");
const [response, setResponse] = useState("");
const [isSpeaking, setIsSpeaking] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const recognitionRef = useRef<any>(null);
const synthRef = useRef<any>(null);
useEffect(() => {
const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
const synth = window.speechSynthesis;
if (SpeechRecognition) {
recognitionRef.current = new SpeechRecognition();
recognitionRef.current.continuous = false;
recognitionRef.current.interimResults = true;
recognitionRef.current.lang = "es-ES";
recognitionRef.current.onresult = (event: any) => {
const last = event.results.length - 1;
const text = event.results[last][0].transcript;
setTranscript(text);
if (event.results[last].isFinal) {
handleSend(text);
}
};
recognitionRef.current.onend = () => {
setIsListening(false);
};
}
if (synth) {
synthRef.current = synth;
}
return () => {
if (recognitionRef.current) recognitionRef.current.abort();
};
}, []);
const startListening = () => {
if (recognitionRef.current && !isListening) {
setTranscript("");
recognitionRef.current.start();
setIsListening(true);
}
};
const stopListening = () => {
if (recognitionRef.current && isListening) {
recognitionRef.current.stop();
setIsListening(false);
}
};
const handleSend = async (text: string) => {
if (!text.trim()) return;
setIsLoading(true);
setResponse("Pensando...");
try {
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text })
});
const data = await res.json();
const reply = (data.response || "¡Hola! Estoy aquí. ¿En qué puedo ayudarte?").substring(0, 200);
setResponse(reply);
speak(reply);
} catch (e) {
console.error(e);
setResponse("Tengo problemas para conectar. Intenta de nuevo.");
}
setIsLoading(false);
};
const speak = (text: string) => {
if (!synthRef.current) return;
synthRef.current.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = "es-ES";
utterance.rate = 0.85;
utterance.pitch = 0.9;
const voices = synthRef.current.getVoices();
const spanishVoice = voices.find((v: any) => v.lang.includes("es"));
if (spanishVoice) utterance.voice = spanishVoice;
utterance.onstart = () => setIsSpeaking(true);
utterance.onend = () => setIsSpeaking(false);
utterance.onerror = () => setIsSpeaking(false);
synthRef.current.speak(utterance);
};
return (
<div className="bg-gradient-to-br from-purple-900 to-indigo-900 p-6 rounded-2xl shadow-2xl max-w-md mx-auto">
<div className="text-center mb-6">
<div className="text-4xl mb-2">👑</div>
<h3 className="text-xl font-bold text-white">Cleopatra Voice</h3>
<p className="text-purple-200 text-sm">Asistente de Ventas IA</p>
</div>
<div className="flex justify-center mb-4">
<div className={`px-4 py-2 rounded-full text-sm ${
isListening ? "bg-red-500 animate-pulse" :
isSpeaking ? "bg-green-500 animate-pulse" :
isLoading ? "bg-yellow-500" :
"bg-gray-600"
} text-white`}>
{isListening ? "🎤 Escuchando..." :
isSpeaking ? "🔊 Hablando..." :
isLoading ? "⏳ Pensando..." :
"💬 Lista"}
</div>
</div>
<div className="h-16 flex items-center justify-center mb-4 gap-1">
{[...Array(12)].map((_, i) => (
<div
key={i}
className={`w-1 bg-purple-400 rounded-full transition-all duration-100 ${
isListening || isSpeaking ? "animate-pulse" : ""
}`}
style={{
height: isListening || isSpeaking ? `${20 + Math.random() * 40}px` : "8px",
animationDelay: `${i * 50}ms`
}}
/>
))}
</div>
{transcript && (
<div className="bg-black/30 rounded-lg p-3 mb-3">
<p className="text-xs text-purple-300">Dijiste:</p>
<p className="text-white">{transcript}</p>
</div>
)}
{response && (
<div className="bg-purple-800/50 rounded-lg p-3 mb-4">
<p className="text-xs text-green-300">Cleopatra:</p>
<p className="text-white">{response}</p>
</div>
)}
<div className="flex justify-center gap-3">
<button
onClick={isListening ? stopListening : startListening}
className={`w-16 h-16 rounded-full flex items-center justify-center text-2xl transition-all ${
isListening
? "bg-red-500 hover:bg-red-600 animate-pulse"
: "bg-purple-500 hover:bg-purple-600"
} text-white shadow-lg`}
>
{isListening ? "⏹" : "🎤"}
</button>
<button
onClick={() => speak(response || "¡Hola! Soy Cleopatra. ¿En qué puedo ayudarte?")}
disabled={isSpeaking || !response}
className="w-12 h-12 rounded-full bg-blue-500 hover:bg-blue-600 disabled:opacity-50 flex items-center justify-center text-xl"
>
🔊
</button>
</div>
<p className="text-center text-xs text-purple-300 mt-4">
{isListening ? "Habla ahora..." : "Toca el micrófono para empezar"}
</p>
</div>
);
}