|
1
|
import { useState } from "react"; |
|
2
|
import { Button } from "@/components/ui/button"; |
|
3
|
import { Input } from "@/components/ui/input"; |
|
4
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; |
|
5
|
import { Bot, Send, X, User, Sparkles } from "lucide-react"; |
|
6
|
import { motion, AnimatePresence } from "framer-motion"; |
|
7
|
import { ScrollArea } from "@/components/ui/scroll-area"; |
|
8
|
|
|
9
|
type Message = { |
|
10
|
role: "user" | "assistant"; |
|
11
|
content: string; |
|
12
|
}; |
|
13
|
|
|
14
|
export default function AIAssistant() { |
|
15
|
const [isOpen, setIsOpen] = useState(false); |
|
16
|
const [messages, setMessages] = useState<Message[]>([ |
|
17
|
{ role: "assistant", content: "Hello! I'm your JCRguru AI Assistant. How can I help you with your repair today?" } |
|
18
|
]); |
|
19
|
const [input, setInput] = useState(""); |
|
20
|
const [isTyping, setIsTyping] = useState(false); |
|
21
|
|
|
22
|
const handleSend = async () => { |
|
23
|
if (!input.trim()) return; |
|
24
|
|
|
25
|
const userMessage: Message = { role: "user", content: input }; |
|
26
|
setMessages(prev => [...prev, userMessage]); |
|
27
|
setInput(""); |
|
28
|
setIsTyping(true); |
|
29
|
|
|
30
|
// Mock AI Response - In production, this would call your server/AI API |
|
31
|
setTimeout(() => { |
|
32
|
const assistantMessage: Message = { |
|
33
|
role: "assistant", |
|
34
|
content: `I've analyzed your query about "${input}". Based on our technical database, I recommend checking the voltage rails first. Would you like me to pull up the relevant circuit diagram?` |
|
35
|
}; |
|
36
|
setMessages(prev => [...prev, assistantMessage]); |
|
37
|
setIsTyping(false); |
|
38
|
}, 1500); |
|
39
|
}; |
|
40
|
|
|
41
|
return ( |
|
42
|
<> |
|
43
|
{/* Floating Toggle Button */} |
|
44
|
<button |
|
45
|
onClick={() => setIsOpen(true)} |
|
46
|
className="fixed bottom-24 right-6 h-14 w-14 rounded-2xl bg-primary text-white shadow-glass flex items-center justify-center hover:scale-110 transition-transform z-40" |
|
47
|
> |
|
48
|
<Bot className="h-6 w-6" /> |
|
49
|
<div className="absolute -top-1 -right-1 h-4 w-4 bg-emerald-500 rounded-full border-2 border-background animate-pulse" /> |
|
50
|
</button> |
|
51
|
|
|
52
|
<AnimatePresence> |
|
53
|
{isOpen && ( |
|
54
|
<motion.div |
|
55
|
initial={{ opacity: 0, y: 20, scale: 0.95 }} |
|
56
|
animate={{ opacity: 1, y: 0, scale: 1 }} |
|
57
|
exit={{ opacity: 0, y: 20, scale: 0.95 }} |
|
58
|
className="fixed bottom-24 right-6 w-[90vw] max-w-[400px] h-[500px] z-50 flex flex-col" |
|
59
|
> |
|
60
|
<Card className="flex-1 border-border/40 shadow-glass-lg rounded-[2.5rem] overflow-hidden bg-card/60 backdrop-blur-3xl flex flex-col"> |
|
61
|
<CardHeader className="bg-primary/10 border-b border-border/40 py-4 flex flex-row items-center justify-between"> |
|
62
|
<div className="flex items-center gap-3"> |
|
63
|
<div className="h-10 w-10 rounded-xl bg-primary flex items-center justify-center"> |
|
64
|
<Bot className="h-5 w-5 text-white" /> |
|
65
|
</div> |
|
66
|
<div> |
|
67
|
<CardTitle className="text-sm font-bold">Technical AI</CardTitle> |
|
68
|
<div className="flex items-center gap-1 text-[10px] text-emerald-500 font-bold uppercase"> |
|
69
|
<div className="h-1.5 w-1.5 rounded-full bg-emerald-500" /> Online |
|
70
|
</div> |
|
71
|
</div> |
|
72
|
</div> |
|
73
|
<Button variant="ghost" size="icon" onClick={() => setIsOpen(false)} className="rounded-full h-8 w-8"> |
|
74
|
<X className="h-4 w-4" /> |
|
75
|
</Button> |
|
76
|
</CardHeader> |
|
77
|
|
|
78
|
<CardContent className="flex-1 flex flex-col p-0"> |
|
79
|
<ScrollArea className="flex-1 p-4"> |
|
80
|
<div className="space-y-4"> |
|
81
|
{messages.map((msg, i) => ( |
|
82
|
<div key={i} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}> |
|
83
|
<div className={`max-w-[80%] p-4 rounded-[1.5rem] text-sm ${ |
|
84
|
msg.role === "user" |
|
85
|
? "bg-primary text-white rounded-tr-none" |
|
86
|
: "bg-muted/40 backdrop-blur-sm rounded-tl-none" |
|
87
|
}`}> |
|
88
|
{msg.content} |
|
89
|
</div> |
|
90
|
</div> |
|
91
|
))} |
|
92
|
{isTyping && ( |
|
93
|
<div className="flex justify-start"> |
|
94
|
<div className="bg-muted/40 p-4 rounded-[1.5rem] rounded-tl-none flex gap-1"> |
|
95
|
<div className="h-1.5 w-1.5 bg-muted-foreground/40 rounded-full animate-bounce" /> |
|
96
|
<div className="h-1.5 w-1.5 bg-muted-foreground/40 rounded-full animate-bounce delay-100" /> |
|
97
|
<div className="h-1.5 w-1.5 bg-muted-foreground/40 rounded-full animate-bounce delay-200" /> |
|
98
|
</div> |
|
99
|
</div> |
|
100
|
)} |
|
101
|
</div> |
|
102
|
</ScrollArea> |
|
103
|
|
|
104
|
<div className="p-4 border-t border-border/40 bg-muted/20"> |
|
105
|
<div className="flex gap-2"> |
|
106
|
<Input |
|
107
|
value={input} |
|
108
|
onChange={(e) => setInput(e.target.value)} |
|
109
|
onKeyPress={(e) => e.key === "Enter" && handleSend()} |
|
110
|
placeholder="Ask for repair help..." |
|
111
|
className="rounded-xl bg-background border-none focus-visible:ring-primary/20 h-11" |
|
112
|
/> |
|
113
|
<Button onClick={handleSend} className="rounded-xl h-11 w-11 p-0 shadow-lg"> |
|
114
|
<Send className="h-4 w-4" /> |
|
115
|
</Button> |
|
116
|
</div> |
|
117
|
</div> |
|
118
|
</CardContent> |
|
119
|
</Card> |
|
120
|
</motion.div> |
|
121
|
)} |
|
122
|
</AnimatePresence> |
|
123
|
</> |
|
124
|
); |
|
125
|
} |