"use client"; import { useState, useRef, useEffect } from "react"; import Vapi from "@vapi-ai/web"; const VAPI_PUBLIC_KEY = "ee086729-fc5c-447e-9a40-840f44bc006b"; const ASSISTANT_ID = "92630ca5-e165-4360-bce0-dd8730882569"; interface SiteMenteVoiceWidgetProps { businessName?: string; businessType?: "restaurant" | "real-estate" | "clinic" | "car-rental" | "default"; theme?: "dark" | "light"; initialLang?: string; } type Mode = "text" | "voice"; export default function SiteMenteVoiceWidget({ businessName = "SiteMente", businessType = "default", theme = "dark", initialLang = "es" }: SiteMenteVoiceWidgetProps) { const [mode, setMode] = useState("text"); const [isActive, setIsActive] = useState(false); const [status, setStatus] = useState<"idle" | "connecting" | "active" | "error">("idle"); const [transcript, setTranscript] = useState(""); const [errorMsg, setErrorMsg] = useState(""); // Text chat state const [messages, setMessages] = useState<{role: "user" | "assistant", content: string}[]>([]); const [input, setInput] = useState(""); const [isSending, setIsSending] = useState(false); const vapiRef = useRef(null); const messagesEndRef = useRef(null); // Auto-hide chat when switching to voice mode useEffect(() => { if (mode === "voice") { setShowChat(false); } }, [mode]); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); // Voice mode functions const startCall = async () => { try { console.log("Starting voice call..."); setErrorMsg(""); setStatus("connecting"); // Verify mic try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); console.log("✅ Mic stream created"); } catch (micErr) { console.log("❌ Mic error:", micErr); setErrorMsg("Microphone access denied"); setStatus("error"); return; } const vapi = new Vapi(VAPI_PUBLIC_KEY); vapiRef.current = vapi; vapi.on("error", (error: any) => { console.log("Vapi error:", error); const msg = String(error?.message || error?.error?.message || "Voice call failed"); setErrorMsg(msg); setStatus("error"); setIsActive(false); }); vapi.on("call-start", () => { console.log("✅ Call started!"); setStatus("active"); }); vapi.on("call-end", () => { console.log("Call ended"); setStatus("idle"); setIsActive(false); }); vapi.on("transcript", (transcript: any) => { const text = typeof transcript === "string" ? transcript : transcript?.text || ""; setTranscript(text); }); await vapi.start(ASSISTANT_ID); setIsActive(true); } catch (error: any) { console.log("Start error:", error); const msg = String(error?.message || "Failed to start call"); setErrorMsg(msg); setStatus("error"); } }; const endCall = async () => { try { if (vapiRef.current) { await vapiRef.current.stop(); } setIsActive(false); setStatus("idle"); setTranscript(""); } catch (error) { console.error("End call error:", error); } }; // Text mode functions const sendMessage = async () => { if (!input.trim() || isSending) return; const userMessage = input.trim(); setInput(""); setIsSending(true); // Add user message setMessages(prev => [...prev, { role: "user", content: userMessage }]); try { // TODO: Replace with actual AI API call // For now, simulate response await new Promise(resolve => setTimeout(resolve, 1000)); // Spanish responses const spanishResponses: Record = { "hola": `¡Hola! 👋 Soy el asistente de ${businessName}. ¿En qué puedo ayudarte hoy?`, "horario": `Nuestros horarios de atención son de lunes a domingo. ¿Tienes alguna pregunta específica?`, "reservar": "Para hacer una reserva, puedo ayudarte ahora mismo. ¿Qué servicio te interesa?", "contacto": `Puedes llamarnos al +34 XXX XXX XXX o escribirnos aquí.`, }; // English responses const englishResponses: Record = { "hello": `Hello! 👋 I'm the AI assistant for ${businessName}. How can I help you today?`, "hours": `We're open Monday to Sunday. Do you have a specific question?`, "book": "I can help you book right now. What service are you interested in?", "contact": `You can call us at +34 XXX XXX XXX or message us here.`, "price": "We offer packages starting from €299/month. Would you like me to tell you more?", "cost": "We offer packages starting from €299/month. Would you like me to tell you more?", "menu": "We serve delicious food daily. Would you like to see our menu or make a reservation?", "reservation": "I can help you make a reservation! What date and time works for you?", "thanks": "You're welcome! Is there anything else I can help you with?", "thank you": "You're welcome! Is there anything else I can help you with?", }; // Default responses const spanishDefault = "Gracias por tu mensaje. Un miembro de nuestro equipo te responderá pronto. ¿Hay algo específico en lo que pueda ayudarte?"; const englishDefault = "Thanks for your message! A team member will get back to you shortly. Is there anything specific I can help you with?"; const lowerInput = userMessage.toLowerCase(); // Detect language (simple check for English keywords) const isEnglish = /^(hello|hi|hours|book|contact|price|cost|menu|reservation|thanks|thank you|what|how|when|where)/i.test(lowerInput); const responses = isEnglish ? englishResponses : spanishResponses; const defaultResponse = isEnglish ? englishDefault : spanishDefault; let response = defaultResponse; for (const [key, value] of Object.entries(responses)) { if (lowerInput.includes(key)) { response = value; break; } } setMessages(prev => [...prev, { role: "assistant", content: response }]); } catch (error) { console.error("Send error:", error); setMessages(prev => [...prev, { role: "assistant", content: "Lo siento, hubo un error. Inténtalo de nuevo." }]); } finally { setIsSending(false); } }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); } }; const toggleMode = () => { // End any active call when switching if (isActive) { endCall(); } setShowChat(mode === "voice"); // Show chat when switching to text setMode(prev => prev === "text" ? "voice" : "text"); }; // Add state for chat panel visibility const [showChat, setShowChat] = useState(true); const buttonColor = theme === "dark" ? "bg-brand-pink" : "bg-blue-600"; const bgColor = theme === "dark" ? "bg-[#1a1625]" : "bg-white"; const textColor = theme === "dark" ? "text-white" : "text-gray-900"; const inputBg = theme === "dark" ? "bg-white/10" : "bg-gray-100"; return (
{/* Mode Toggle Button */} {/* Error Message */} {status === "error" && errorMsg && (
⚠️ {errorMsg}
)} {/* Chat Panel - Text Mode */} {mode === "text" && showChat && (
{/* Header */}
💬 {businessName}
{/* Messages */}
{messages.length === 0 && (
👋 ¡Hola! Escríbeme para ayudarte
)} {messages.map((msg, i) => (
{msg.content}
))} {isSending && (
Escribiendo...
)}
{/* Input */}
setInput(e.target.value)} onKeyPress={handleKeyPress} placeholder="Escribe tu mensaje..." className={`flex-1 px-3 py-2 rounded-lg text-sm ${inputBg} ${textColor} placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-brand-pink`} disabled={isSending} />
)} {/* Voice Mode Panel */} {mode === "voice" && isActive && (
🎙️ Voice Chat
{transcript || "Listening..."}
)} {/* Main Button */} {mode === "voice" && status === "connecting" && (
Connecting...
)}
); }