Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Failing after 9m26s
108 lines
4.2 KiB
TypeScript
108 lines
4.2 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect, useRef } from "react";
|
|
import { Send, Loader2 } from "lucide-react";
|
|
|
|
interface Message {
|
|
id: string;
|
|
text: string;
|
|
sender: "user" | "bot";
|
|
timestamp: Date;
|
|
}
|
|
|
|
interface StoredMessage {
|
|
id: string;
|
|
text: string;
|
|
sender: "user" | "bot";
|
|
timestamp: string;
|
|
}
|
|
|
|
export default function BentoChat() {
|
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
const [inputValue, setInputValue] = useState("");
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [conversationId, setConversationId] = useState<string>("default");
|
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
try {
|
|
const storedId = localStorage.getItem("chatSessionId");
|
|
if (storedId) setConversationId(storedId);
|
|
else {
|
|
const newId = crypto.randomUUID();
|
|
localStorage.setItem("chatSessionId", newId);
|
|
setConversationId(newId);
|
|
}
|
|
|
|
const storedMsgs = localStorage.getItem("chatMessages");
|
|
if (storedMsgs) {
|
|
setMessages(JSON.parse(storedMsgs).map((m: StoredMessage) => ({ ...m, timestamp: new Date(m.timestamp) })));
|
|
} else {
|
|
setMessages([{ id: "welcome", text: "Hi! Ask me anything about Dennis! 🚀", sender: "bot", timestamp: new Date() }]);
|
|
}
|
|
} catch {}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (messages.length > 0) localStorage.setItem("chatMessages", JSON.stringify(messages));
|
|
scrollRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
}, [messages]);
|
|
|
|
const handleSend = async () => {
|
|
if (!inputValue.trim() || isLoading) return;
|
|
const userMsg: Message = { id: Date.now().toString(), text: inputValue.trim(), sender: "user", timestamp: new Date() };
|
|
setMessages(prev => [...prev, userMsg]);
|
|
setInputValue("");
|
|
setIsLoading(true);
|
|
|
|
try {
|
|
const res = await fetch("/api/n8n/chat", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ message: userMsg.text, conversationId, history: messages.slice(-5).map(m => ({ role: m.sender === "user" ? "user" : "assistant", content: m.text })) }),
|
|
});
|
|
const data = await res.json();
|
|
setMessages(prev => [...prev, { id: (Date.now() + 1).toString(), text: data.reply || "Error", sender: "bot", timestamp: new Date() }]);
|
|
} catch {
|
|
setMessages(prev => [...prev, { id: (Date.now() + 1).toString(), text: "Connection error.", sender: "bot", timestamp: new Date() }]);
|
|
} finally {
|
|
setIsLoading(true);
|
|
setTimeout(() => setIsLoading(false), 500); // Small delay for feel
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-full min-h-[300px]">
|
|
<div className="flex-1 overflow-y-auto pr-2 scrollbar-hide space-y-4 mb-4">
|
|
{messages.map((m) => (
|
|
<div key={m.id} className={`flex ${m.sender === "user" ? "justify-end" : "justify-start"}`}>
|
|
<div className={`max-w-[90%] rounded-2xl px-4 py-2 text-sm shadow-sm ${m.sender === "user" ? "bg-liquid-purple text-white" : "bg-white dark:bg-stone-800 text-stone-900 dark:text-stone-100 border border-stone-100 dark:border-stone-700"}`}>
|
|
{m.text}
|
|
</div>
|
|
</div>
|
|
))}
|
|
{isLoading && (
|
|
<div className="flex justify-start">
|
|
<div className="bg-stone-100 dark:bg-stone-800 rounded-2xl px-4 py-2"><Loader2 size={14} className="animate-spin text-stone-400" /></div>
|
|
</div>
|
|
)}
|
|
<div ref={scrollRef} />
|
|
</div>
|
|
|
|
<div className="relative">
|
|
<input
|
|
type="text"
|
|
value={inputValue}
|
|
onChange={(e) => setInputValue(e.target.value)}
|
|
onKeyDown={(e) => e.key === "Enter" && handleSend()}
|
|
placeholder="Ask me..."
|
|
className="w-full bg-white dark:bg-stone-800 border border-stone-200 dark:border-stone-700 rounded-2xl py-3 pl-4 pr-12 text-sm focus:outline-none focus:ring-2 focus:ring-liquid-purple/30 transition-all shadow-inner dark:text-white"
|
|
/>
|
|
<button onClick={handleSend} className="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-liquid-purple hover:scale-110 transition-transform">
|
|
<Send size={18} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|