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
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user