import { useState, useRef, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { MessageCircle, X, Send, Bot, User, Loader2 } from "lucide-react"; import { useLocation } from "wouter"; import { isMobile } from "@/lib/platform"; interface Message { id: string; role: "user" | "assistant"; content: string; timestamp: Date; } export function Chatbot() { const [location] = useLocation(); const [isOpen, setIsOpen] = useState(false); const [messages, setMessages] = useState([ { id: "welcome", role: "assistant", content: "AEGIS ONLINE. Security protocols initialized. Neural link established. How can I assist with your security operations today?", timestamp: new Date(), }, ]); const [input, setInput] = useState(""); const [isLoading, setIsLoading] = useState(false); const messagesEndRef = useRef(null); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); // Load chat history when opening the chatbot useEffect(() => { if (isOpen) { loadChatHistory(); } }, [isOpen]); const loadChatHistory = async () => { try { const response = await fetch("/api/chat/history", { method: "GET", credentials: "include", }); if (response.ok) { const data = await response.json(); if (data.history && data.history.length > 0) { const historyMessages: Message[] = data.history.map((msg: any) => ({ id: msg.id, role: msg.role, content: msg.content, timestamp: new Date(msg.created_at), })); // Replace welcome message with loaded history setMessages(prev => [...historyMessages, ...prev.slice(1)]); } } } catch (error) { console.error("Failed to load chat history:", error); } }; // Don't render chatbot on the OS page - it has its own environment if (location === "/os" || isMobile()) { return null; } const sendMessage = async () => { if (!input.trim() || isLoading) return; const userMessage: Message = { id: Date.now().toString(), role: "user", content: input.trim(), timestamp: new Date(), }; setMessages((prev) => [...prev, userMessage]); setInput(""); setIsLoading(true); try { const conversationHistory = messages.slice(-10).map(m => ({ role: m.role, content: m.content })); const response = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ message: userMessage.content, history: conversationHistory }), }); if (!response.ok) throw new Error("Failed to get response"); const data = await response.json(); const assistantMessage: Message = { id: (Date.now() + 1).toString(), role: "assistant", content: data.response || "I apologize, but I'm having trouble responding right now. Please try again.", timestamp: new Date(), }; setMessages((prev) => [...prev, assistantMessage]); } catch (error) { const errorMessage: Message = { id: (Date.now() + 1).toString(), role: "assistant", content: "I'm sorry, I encountered an error. Please try again in a moment.", timestamp: new Date(), }; setMessages((prev) => [...prev, errorMessage]); } finally { setIsLoading(false); } }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); } }; return ( <> {/* Chat Button */} {!isOpen && ( setIsOpen(true)} className="fixed bottom-6 left-6 z-50 bg-secondary text-background p-4 rounded-full shadow-lg hover:bg-secondary/90 transition-colors" data-testid="button-open-chatbot" > )} {/* Chat Window */} {isOpen && ( {/* Header */}
AEGIS
Security Active
{/* Messages */}
{messages.map((message) => (
{message.role === "user" ? ( ) : ( )}
{message.content}
))} {isLoading && (
)}
{/* Input */}
setInput(e.target.value)} onKeyPress={handleKeyPress} placeholder="Ask me anything..." className="flex-1 bg-black/20 border border-white/10 px-4 py-2 text-sm text-white placeholder:text-muted-foreground focus:outline-none focus:border-secondary/50" data-testid="input-chat-message" />
)} ); }