Spaces:
Running
Running
File size: 4,009 Bytes
a985b94 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | import React, { useState, useRef, useEffect } from 'react';
import axios from 'axios';
import API_BASE_URL from '../apiConfig';
export const ChatBot = ({ isOpen, onClose }) => {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const messagesEndRef = useRef(null);
// Dragging state
const [position, setPosition] = useState({
x: typeof window !== 'undefined' ? window.innerWidth - 400 : 0,
y: typeof window !== 'undefined' ? window.innerHeight - 650 : 0
});
const [isDragging, setIsDragging] = useState(false);
const dragRef = useRef({ startX: 0, startY: 0, initialX: 0, initialY: 0 });
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
// Handle Dragging
const handlePointerDown = (e) => {
// Don't drag if clicking the close button
if (e.target.tagName.toLowerCase() === 'button') return;
setIsDragging(true);
dragRef.current = {
startX: e.clientX,
startY: e.clientY,
initialX: position.x,
initialY: position.y
};
e.currentTarget.setPointerCapture(e.pointerId);
};
const handlePointerMove = (e) => {
if (!isDragging) return;
const dx = e.clientX - dragRef.current.startX;
const dy = e.clientY - dragRef.current.startY;
setPosition({
x: dragRef.current.initialX + dx,
y: dragRef.current.initialY + dy
});
};
const handlePointerUp = (e) => {
setIsDragging(false);
e.currentTarget.releasePointerCapture(e.pointerId);
};
const handleSend = async (e) => {
e.preventDefault();
if (!input.trim()) return;
const userMessage = { role: 'user', content: input };
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);
try {
const response = await axios.post(`${API_BASE_URL}/api/chat`, {
messages: [...messages, userMessage].map(m => ({ role: m.role, content: m.content }))
});
if (response.data.response) {
setMessages(prev => [...prev, { role: 'model', content: response.data.response }]);
} else if (response.data.error) {
setMessages(prev => [...prev, { role: 'model', content: `Error: ${response.data.error}` }]);
}
} catch (error) {
console.error('Chat error:', error);
setMessages(prev => [...prev, { role: 'model', content: "GRRR... I couldn't reach the server. Is it running?" }]);
} finally {
setIsLoading(false);
}
};
if (!isOpen) return null;
return (
<div
className="chat-widget-container"
style={{ left: position.x, top: position.y, margin: 0 }}
>
<div
className="chat-header"
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
>
<h3>🦍 Gorilla Bot</h3>
<button className="chat-close-btn" onClick={onClose}>×</button>
</div>
<div className="chat-messages">
{messages.map((msg, idx) => (
<div key={idx} className={`chat-message ${msg.role}`}>
<div className="chat-bubble">
{msg.content}
</div>
</div>
))}
{isLoading && (
<div className="chat-message model">
<div className="chat-bubble loading">
Thinking... 🍌
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
<form className="chat-input-area" onSubmit={handleSend}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Ask about defects, KPIs, or forecasts..."
className="chat-input"
/>
<button type="submit" className="chat-send-btn" disabled={isLoading}>
Send
</button>
</form>
</div>
);
};
|