"use client"; import { Bot, Loader2, Maximize2, Minimize2, Send, Sparkles, X, } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/primitives"; import { useChatbotUserId } from "@/hooks/use-chatbot-user"; import { chatbotApi } from "@/lib/api"; import { formatRelativeTime } from "@/lib/format"; interface ChatMessage { id: string; sender: "user" | "bot"; text: string; timestamp: number; } export function Chatbot() { const [open, setOpen] = useState(false); const [expanded, setExpanded] = useState(false); const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [sending, setSending] = useState(false); const userId = useChatbotUserId(); const containerRef = useRef(null); const scrollRef = useRef(null); // Load history on mount useEffect(() => { chatbotApi .getHistory(userId || undefined) .then((res) => { if (res.history) { const loaded: ChatMessage[] = res.history.flatMap((h) => [ { id: `${h.id}-u`, sender: "user", text: h.user_message, timestamp: new Date(h.created_at).getTime(), }, { id: `${h.id}-b`, sender: "bot", text: h.bot_response, timestamp: new Date(h.created_at).getTime() + 100, }, ]); setMessages(loaded); } }) .catch(() => {}); }, [userId]); // Auto-scroll to bottom on new messages useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }); // CSS animation for floating window open useEffect(() => { const el = containerRef.current; if (!el || !open) return; el.style.animation = "fade-scale-in 0.25s ease-out forwards"; return () => { el.style.removeProperty("animation"); }; }, [open]); const handleSend = async (e: React.FormEvent) => { e.preventDefault(); const query = input.trim(); if (!query || sending) return; const userMsg: ChatMessage = { id: `u-${Date.now()}`, sender: "user", text: query, timestamp: Date.now(), }; setMessages((prev) => [...prev, userMsg]); setInput(""); setSending(true); try { const res = await chatbotApi.send(query, undefined, userId || "operator"); const botMsg: ChatMessage = { id: `b-${Date.now()}`, sender: "bot", text: res.response || "No response received.", timestamp: Date.now(), }; setMessages((prev) => [...prev, botMsg]); } catch { const errMsg: ChatMessage = { id: `err-${Date.now()}`, sender: "bot", text: "Signal telemetry fault. Failed to communicate with neural core.", timestamp: Date.now(), }; setMessages((prev) => [...prev, errMsg]); } finally { setSending(false); } }; return ( <> {/* Floating Tactical Launcher Button */} {!open && ( )} {/* Neural Assistant Dialog */} {open && (
{/* Header */}
Neural Core Assistant ● ACTIVE
{/* Messages Body */}
{messages.length === 0 ? (
Neural Assistant ready. Ask about telemetry, moderation policies, or channel activity.
) : ( messages.map((m) => { const isUser = m.sender === "user"; return (
{m.text}
{formatRelativeTime(m.timestamp)}
); }) )} {sending && (
Processing vector inference...
)}
{/* Input Footer */}
setInput(e.target.value)} className="min-h-[38px] flex-1 rounded-[6px] border border-hairline bg-surface-2 px-3 py-1.5 font-mono text-xs text-ink placeholder:text-ink-faint focus:border-signal focus:outline-none" />
)} ); }