Skip to content

Commit bf000a2

Browse files
committed
Update the chatbot
1 parent 1fcab95 commit bf000a2

File tree

3 files changed

+172
-1
lines changed

3 files changed

+172
-1
lines changed

app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ScrollToTop from "@/components/ScrollToTop";
66
import ThemeScript from "@/components/ThemeScript";
77
import Navbar from "@/components/Navbar";
88
import Footer from "@/components/Footer";
9+
import Chatbot from "@/components/Chatbot";
910

1011
const inter = Inter({
1112
subsets: ["latin"],
@@ -36,6 +37,7 @@ export default function RootLayout({
3637
</div>
3738
<Footer />
3839
<ScrollToTop />
40+
<Chatbot />
3941
</body>
4042
</html>
4143
);

components/Chatbot.tsx

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
'use client';
2+
3+
import { useState, useRef, useEffect } from 'react';
4+
5+
export default function Chatbot() {
6+
const [isOpen, setIsOpen] = useState(false);
7+
const [messages, setMessages] = useState<Array<{ role: 'user' | 'assistant'; content: string }>>([
8+
{
9+
role: 'assistant',
10+
content: 'Hello! I can help you with questions about Rust blockchain development. What would you like to know?'
11+
}
12+
]);
13+
const [inputValue, setInputValue] = useState('');
14+
const messagesEndRef = useRef<HTMLDivElement>(null);
15+
const inputRef = useRef<HTMLInputElement>(null);
16+
17+
const scrollToBottom = () => {
18+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
19+
};
20+
21+
useEffect(() => {
22+
if (isOpen) {
23+
scrollToBottom();
24+
// Focus on input when chat opens
25+
setTimeout(() => inputRef.current?.focus(), 100);
26+
}
27+
}, [isOpen, messages]);
28+
29+
const handleSendMessage = (e: React.FormEvent) => {
30+
e.preventDefault();
31+
if (!inputValue.trim()) return;
32+
33+
const userMessage = { role: 'user' as const, content: inputValue };
34+
setMessages(prev => [...prev, userMessage]);
35+
setInputValue('');
36+
37+
// Simulate bot response (you can replace this with actual API call)
38+
setTimeout(() => {
39+
const botResponse = {
40+
role: 'assistant' as const,
41+
content: 'Thank you for your question! This is a demo response. You can later integrate the chatbot functionality with a real AI service (e.g., OpenAI, Anthropic, etc.).'
42+
};
43+
setMessages(prev => [...prev, botResponse]);
44+
}, 500);
45+
};
46+
47+
return (
48+
<>
49+
{/* Chat Button */}
50+
<button
51+
onClick={() => setIsOpen(!isOpen)}
52+
className={`fixed bottom-8 right-8 z-50 w-16 h-16 bg-gradient-to-br from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white rounded-full shadow-2xl shadow-orange-500/50 hover:shadow-orange-500/70 flex items-center justify-center transform hover:scale-110 transition-all duration-300 ${
53+
isOpen ? 'rotate-90' : ''
54+
}`}
55+
aria-label={isOpen ? 'Close chat' : 'Open chat'}
56+
>
57+
{isOpen ? (
58+
<svg
59+
className="w-6 h-6"
60+
fill="none"
61+
stroke="currentColor"
62+
viewBox="0 0 24 24"
63+
>
64+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
65+
</svg>
66+
) : (
67+
<svg
68+
className="w-6 h-6"
69+
fill="none"
70+
stroke="currentColor"
71+
viewBox="0 0 24 24"
72+
>
73+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
74+
</svg>
75+
)}
76+
</button>
77+
78+
{/* Chat Window */}
79+
{isOpen && (
80+
<div className="fixed bottom-24 right-8 z-50 w-96 h-[600px] bg-white dark:bg-gray-900 rounded-2xl shadow-2xl border border-gray-200 dark:border-gray-800 flex flex-col overflow-hidden backdrop-blur-xl bg-white/95 dark:bg-gray-900/95 animate-in slide-in-from-bottom-4 duration-300">
81+
{/* Header */}
82+
<div className="bg-gradient-to-r from-orange-500 to-orange-600 text-white p-4 flex items-center justify-between">
83+
<div className="flex items-center gap-3">
84+
<div className="w-10 h-10 bg-white/20 rounded-full flex items-center justify-center">
85+
<svg
86+
className="w-6 h-6"
87+
fill="none"
88+
stroke="currentColor"
89+
viewBox="0 0 24 24"
90+
>
91+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
92+
</svg>
93+
</div>
94+
<div>
95+
<h3 className="font-bold text-lg">Rust Chatbot</h3>
96+
<p className="text-xs text-orange-100">Online</p>
97+
</div>
98+
</div>
99+
<button
100+
onClick={() => setIsOpen(false)}
101+
className="w-8 h-8 rounded-full hover:bg-white/20 flex items-center justify-center transition-colors"
102+
aria-label="Close"
103+
>
104+
<svg
105+
className="w-5 h-5"
106+
fill="none"
107+
stroke="currentColor"
108+
viewBox="0 0 24 24"
109+
>
110+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
111+
</svg>
112+
</button>
113+
</div>
114+
115+
{/* Messages */}
116+
<div className="flex-1 overflow-y-auto p-4 space-y-4">
117+
{messages.map((message, index) => (
118+
<div
119+
key={index}
120+
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
121+
>
122+
<div
123+
className={`max-w-[80%] rounded-2xl px-4 py-2 ${
124+
message.role === 'user'
125+
? 'bg-gradient-to-r from-orange-500 to-orange-600 text-white'
126+
: 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100'
127+
}`}
128+
>
129+
<p className="text-sm whitespace-pre-wrap break-words">{message.content}</p>
130+
</div>
131+
</div>
132+
))}
133+
<div ref={messagesEndRef} />
134+
</div>
135+
136+
{/* Input */}
137+
<form onSubmit={handleSendMessage} className="p-4 border-t border-gray-200 dark:border-gray-800">
138+
<div className="flex gap-2">
139+
<input
140+
ref={inputRef}
141+
type="text"
142+
value={inputValue}
143+
onChange={(e) => setInputValue(e.target.value)}
144+
placeholder="Type a message..."
145+
className="flex-1 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 rounded-xl border border-gray-200 dark:border-gray-700 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent transition-all"
146+
/>
147+
<button
148+
type="submit"
149+
disabled={!inputValue.trim()}
150+
className="px-4 py-2 bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white rounded-xl disabled:opacity-50 disabled:cursor-not-allowed transition-all transform hover:scale-105"
151+
aria-label="Send message"
152+
>
153+
<svg
154+
className="w-5 h-5"
155+
fill="none"
156+
stroke="currentColor"
157+
viewBox="0 0 24 24"
158+
>
159+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
160+
</svg>
161+
</button>
162+
</div>
163+
</form>
164+
</div>
165+
)}
166+
</>
167+
);
168+
}
169+

components/ScrollToTop.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default function ScrollToTop() {
3030
{isVisible && (
3131
<button
3232
onClick={scrollToTop}
33-
className="fixed bottom-8 right-8 z-50 w-14 h-14 bg-gradient-to-br from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white rounded-full shadow-2xl shadow-orange-500/50 hover:shadow-orange-500/70 flex items-center justify-center transform hover:scale-110 transition-all duration-300 group"
33+
className="fixed bottom-8 left-8 z-50 w-14 h-14 bg-gradient-to-br from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white rounded-full shadow-2xl shadow-orange-500/50 hover:shadow-orange-500/70 flex items-center justify-center transform hover:scale-110 transition-all duration-300 group"
3434
aria-label="Scroll to top"
3535
>
3636
<svg

0 commit comments

Comments
 (0)