"use client"; import React, { useState, useEffect, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { MessageCircle, X, Send, Loader2, Sparkles, Trash2, } from "lucide-react"; interface Message { id: string; text: string; sender: "user" | "bot"; timestamp: Date; isTyping?: boolean; } export default function ChatWidget() { const [isOpen, setIsOpen] = useState(false); const [messages, setMessages] = useState([]); const [inputValue, setInputValue] = useState(""); const [isLoading, setIsLoading] = useState(false); const [conversationId, setConversationId] = useState(() => { // Generate or retrieve conversation ID if (typeof window !== "undefined") { const stored = localStorage.getItem("chatSessionId"); if (stored) return stored; const newId = crypto.randomUUID(); localStorage.setItem("chatSessionId", newId); return newId; } return "default"; }); const messagesEndRef = useRef(null); const inputRef = useRef(null); // Auto-scroll to bottom when new messages arrive useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); // Focus input when chat opens useEffect(() => { if (isOpen) { inputRef.current?.focus(); } }, [isOpen]); // Helper function to decode HTML entities const decodeHtmlEntities = (text: string): string => { if (!text || typeof text !== 'string') return text; const textarea = document.createElement('textarea'); textarea.innerHTML = text; return textarea.value; }; // Load messages from localStorage useEffect(() => { if (typeof window !== "undefined") { const stored = localStorage.getItem("chatMessages"); if (stored) { try { const parsed = JSON.parse(stored); setMessages( parsed.map((m: Message) => ({ ...m, text: decodeHtmlEntities(m.text), // Decode HTML entities when loading timestamp: new Date(m.timestamp), })), ); } catch (e) { console.error("Failed to load chat history", e); } } else { // Add welcome message setMessages([ { id: "welcome", text: "Hi! I'm Dennis's AI assistant. Ask me anything about his skills, projects, or experience! 🚀", sender: "bot", timestamp: new Date(), }, ]); } } }, []); // Save messages to localStorage useEffect(() => { if (typeof window !== "undefined" && messages.length > 0) { localStorage.setItem("chatMessages", JSON.stringify(messages)); } }, [messages]); const handleSend = async () => { if (!inputValue.trim() || isLoading) return; const userMessage: Message = { id: Date.now().toString(), text: inputValue.trim(), sender: "user", timestamp: new Date(), }; setMessages((prev) => [...prev, userMessage]); setInputValue(""); setIsLoading(true); // Get last 10 messages for context const history = messages.slice(-10).map((m) => ({ role: m.sender === "user" ? "user" : "assistant", content: m.text, })); try { const response = await fetch("/api/n8n/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: userMessage.text, conversationId, history, }), }); if (!response.ok) { throw new Error("Failed to get response"); } const data = await response.json(); // Decode HTML entities in the reply let replyText = data.reply || "Sorry, I couldn't process that. Please try again."; // Decode HTML entities client-side (double safety) replyText = decodeHtmlEntities(replyText); const botMessage: Message = { id: (Date.now() + 1).toString(), text: replyText, sender: "bot", timestamp: new Date(), }; setMessages((prev) => [...prev, botMessage]); } catch (error) { console.error("Chat error:", error); const errorMessage: Message = { id: (Date.now() + 1).toString(), text: "Sorry, I'm having trouble connecting right now. Please try again later or use the contact form below.", sender: "bot", timestamp: new Date(), }; setMessages((prev) => [...prev, errorMessage]); } finally { setIsLoading(false); } }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend(); } }; const clearChat = () => { // Reset session ID const newId = crypto.randomUUID(); setConversationId(newId); if (typeof window !== "undefined") { localStorage.setItem("chatSessionId", newId); localStorage.removeItem("chatMessages"); } setMessages([ { id: "welcome", text: "Conversation restarted! Ask me anything about Dennis! 🚀", sender: "bot", timestamp: new Date(), }, ]); }; return ( <> {/* Chat Button */} {!isOpen && ( setIsOpen(true)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { setIsOpen(true); } }} className="fixed bottom-20 left-4 md:bottom-6 md:left-6 z-30 bg-gradient-to-br from-blue-500 to-purple-600 text-white p-3 rounded-full shadow-2xl hover:shadow-blue-500/50 hover:scale-110 transition-all duration-300 group cursor-pointer" aria-label="Open chat" > {/* Tooltip */} Chat with AI assistant )} {/* Chat Window */} {isOpen && ( {/* Header */}

Dennis{'\''}s AI Assistant

Always online

{/* Messages */}
{messages.map((message) => (

{message.text}

{message.timestamp.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", })}

))} {/* Typing Indicator */} {isLoading && (
)}
{/* Input */}
setInputValue(e.target.value)} onKeyPress={handleKeyPress} placeholder="Ask anything..." disabled={isLoading} className="flex-1 px-3 md:px-4 py-2 text-sm bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white rounded-full border border-gray-200 dark:border-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed" />
{/* Quick Actions */}
{[ "What are Dennis's skills?", "Tell me about his projects", "How can I contact him?", ].map((suggestion, index) => ( ))}
)} ); }