101 lines
4.3 KiB
TypeScript
101 lines
4.3 KiB
TypeScript
|
|
import React, { useState, useRef, useEffect } from 'react';
|
|
import { chatWithExpert } from '../services/geminiService';
|
|
import { Message } from '../types';
|
|
|
|
const ChatBot: React.FC = () => {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [input, setInput] = useState('');
|
|
const [messages, setMessages] = useState<Message[]>([
|
|
{ role: 'model', text: "Welcome to Hanmo! I'm your Technical Assistant. How can I help you with our DCS, SCADA, or field instrument solutions today?" }
|
|
]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (scrollRef.current) {
|
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
}
|
|
}, [messages, isOpen]);
|
|
|
|
const handleSend = async () => {
|
|
if (!input.trim() || isLoading) return;
|
|
|
|
const userMsg = input.trim();
|
|
setInput('');
|
|
setMessages(prev => [...prev, { role: 'user', text: userMsg }]);
|
|
setIsLoading(true);
|
|
|
|
const history = messages.map(m => ({ role: m.role, text: m.text }));
|
|
const responseText = await chatWithExpert(userMsg, history);
|
|
|
|
setMessages(prev => [...prev, { role: 'model', text: responseText }]);
|
|
setIsLoading(false);
|
|
};
|
|
|
|
return (
|
|
<div className="fixed bottom-6 right-6 z-[60]">
|
|
<button
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
className="w-14 h-14 md:w-16 md:h-16 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow-2xl flex items-center justify-center transition-transform hover:scale-110 active:scale-95"
|
|
>
|
|
{isOpen ? <i className="fas fa-times text-2xl"></i> : <i className="fas fa-robot text-2xl"></i>}
|
|
</button>
|
|
|
|
{isOpen && (
|
|
<div className="absolute bottom-20 right-0 w-[calc(100vw-3rem)] md:w-96 h-[500px] max-h-[70vh] bg-white rounded-3xl shadow-2xl flex flex-col overflow-hidden border border-slate-200 animate-in fade-in slide-in-from-bottom-5">
|
|
<div className="p-5 bg-blue-600 text-white font-bold flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<i className="fas fa-microchip"></i>
|
|
<span>Tech Advisor</span>
|
|
</div>
|
|
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
|
|
</div>
|
|
|
|
<div ref={scrollRef} className="flex-1 overflow-y-auto p-5 space-y-4 bg-slate-50">
|
|
{messages.map((m, i) => (
|
|
<div key={i} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
|
<div className={`max-w-[85%] p-4 rounded-2xl text-sm leading-relaxed ${
|
|
m.role === 'user'
|
|
? 'bg-blue-600 text-white rounded-tr-none shadow-md'
|
|
: 'bg-white text-slate-700 shadow-sm border border-slate-100 rounded-tl-none'
|
|
}`}>
|
|
{m.text}
|
|
</div>
|
|
</div>
|
|
))}
|
|
{isLoading && (
|
|
<div className="flex justify-start">
|
|
<div className="bg-white p-4 rounded-2xl border border-slate-100 rounded-tl-none flex gap-1.5 shadow-sm">
|
|
<div className="w-1.5 h-1.5 bg-blue-400 rounded-full animate-bounce"></div>
|
|
<div className="w-1.5 h-1.5 bg-blue-400 rounded-full animate-bounce [animation-delay:0.2s]"></div>
|
|
<div className="w-1.5 h-1.5 bg-blue-400 rounded-full animate-bounce [animation-delay:0.4s]"></div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="p-4 bg-white border-t border-slate-100 flex gap-2">
|
|
<input
|
|
type="text"
|
|
value={input}
|
|
onChange={(e) => setInput(e.target.value)}
|
|
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
|
|
placeholder="Ask a technical question..."
|
|
className="flex-1 bg-slate-100 text-sm p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-600/20"
|
|
/>
|
|
<button
|
|
onClick={handleSend}
|
|
className="bg-blue-600 hover:bg-blue-700 text-white w-12 h-12 rounded-xl flex items-center justify-center transition-colors"
|
|
>
|
|
<i className="fas fa-paper-plane"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ChatBot;
|