"use client";
import { useState, useRef, useEffect } from "react";
import { ChatMessage } from "./ChatMessage";
interface Message {
role: "user" | "assistant";
content: string;
}
const EXAMPLE_PROMPTS = [
"What's the current STX price?",
"Check balance for SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7",
"Show me the top trending tokens",
"What's the current stacking APY?",
"Get me a quote to swap 100 STX to WELSH",
"Generate a fungible token contract called MyToken",
];
export function Chat() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null);
// Auto-scroll to bottom when new messages arrive
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
// Auto-resize textarea
useEffect(() => {
if (inputRef.current) {
inputRef.current.style.height = "auto";
inputRef.current.style.height = `${inputRef.current.scrollHeight}px`;
}
}, [input]);
const sendMessage = async (messageText?: string) => {
const text = messageText || input.trim();
if (!text || loading) return;
setInput("");
setError(null);
// Add user message immediately
const userMessage: Message = { role: "user", content: text };
setMessages((prev) => [...prev, userMessage]);
setLoading(true);
try {
const response = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: text,
history: messages,
}),
});
const data = await response.json();
if (!data.success) {
setError(data.error || "Failed to get response");
return;
}
// Add assistant message
const assistantMessage: Message = {
role: "assistant",
content: data.message,
};
setMessages((prev) => [...prev, assistantMessage]);
} catch (err) {
setError(err instanceof Error ? err.message : "Network error");
} finally {
setLoading(false);
}
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
};
const clearChat = () => {
setMessages([]);
setError(null);
};
return (
<div className="flex flex-col h-screen max-w-4xl mx-auto">
{/* Header */}
<header className="flex items-center justify-between px-6 py-4 border-b bg-white">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-orange-500 to-purple-600 flex items-center justify-center">
<span className="text-white font-bold text-lg">S</span>
</div>
<div>
<h1 className="font-semibold text-gray-900">Stacks Agent</h1>
<p className="text-xs text-gray-500">AI-powered blockchain assistant</p>
</div>
</div>
<button
onClick={clearChat}
className="text-sm text-gray-500 hover:text-gray-700 px-3 py-1 rounded-lg hover:bg-gray-100 transition-colors"
>
Clear chat
</button>
</header>
{/* Messages Area */}
<div className="flex-1 overflow-y-auto px-6 py-4 bg-gray-50">
{messages.length === 0 ? (
<div className="h-full flex flex-col items-center justify-center text-center">
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-orange-500 to-purple-600 flex items-center justify-center mb-4">
<span className="text-white font-bold text-2xl">S</span>
</div>
<h2 className="text-xl font-semibold text-gray-900 mb-2">
Welcome to Stacks Agent
</h2>
<p className="text-gray-500 mb-6 max-w-md">
I can help you check balances, get prices, explore stacking rewards,
and even generate smart contracts. Try asking me something!
</p>
<div className="flex flex-wrap gap-2 justify-center max-w-2xl">
{EXAMPLE_PROMPTS.map((prompt, i) => (
<button
key={i}
onClick={() => sendMessage(prompt)}
className="px-3 py-2 text-sm bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 transition-colors text-gray-700"
>
{prompt}
</button>
))}
</div>
</div>
) : (
<>
{messages.map((msg, i) => (
<ChatMessage key={i} role={msg.role} content={msg.content} />
))}
{loading && (
<div className="flex justify-start mb-4">
<div className="bg-gray-100 rounded-2xl rounded-bl-md px-4 py-3">
<div className="flex items-center gap-2">
<div className="flex gap-1">
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: "0ms" }} />
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: "150ms" }} />
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: "300ms" }} />
</div>
<span className="text-sm text-gray-500">Thinking...</span>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</>
)}
</div>
{/* Error Banner */}
{error && (
<div className="px-6 py-3 bg-red-50 border-t border-red-100">
<p className="text-sm text-red-600">{error}</p>
</div>
)}
{/* Input Area */}
<div className="px-6 py-4 border-t bg-white">
<div className="flex gap-3 items-end">
<div className="flex-1 relative">
<textarea
ref={inputRef}
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Ask about Stacks blockchain..."
rows={1}
className="w-full resize-none rounded-2xl border border-gray-200 px-4 py-3 pr-12 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent max-h-32"
disabled={loading}
/>
</div>
<button
onClick={() => sendMessage()}
disabled={loading || !input.trim()}
className="h-12 w-12 rounded-full bg-blue-600 text-white flex items-center justify-center hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="w-5 h-5"
>
<path d="M3.478 2.404a.75.75 0 0 0-.926.941l2.432 7.905H13.5a.75.75 0 0 1 0 1.5H4.984l-2.432 7.905a.75.75 0 0 0 .926.94 60.519 60.519 0 0 0 18.445-8.986.75.75 0 0 0 0-1.218A60.517 60.517 0 0 0 3.478 2.404Z" />
</svg>
</button>
</div>
<p className="text-xs text-gray-400 mt-2 text-center">
Press Enter to send, Shift+Enter for new line
</p>
</div>
</div>
);
}