Spaces:
Sleeping
Sleeping
Commit ·
2f884df
1
Parent(s): 54b041c
start
Browse files- package.json +0 -0
- src/pages/index.astro +0 -0
- src/pages/src/components/ChatWidget.tsx +63 -0
package.json
ADDED
|
File without changes
|
src/pages/index.astro
ADDED
|
File without changes
|
src/pages/src/components/ChatWidget.tsx
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import { useChat } from 'ai/react';
|
| 3 |
+
import { MessageCircle, X, Send, Loader2, Bot } from 'lucide-react';
|
| 4 |
+
|
| 5 |
+
export default function ChatWidget() {
|
| 6 |
+
const [isOpen, setIsOpen] = useState(false);
|
| 7 |
+
|
| 8 |
+
// Ссылка берется из настроек Space или .env файла
|
| 9 |
+
const apiEndpoint = import.meta.env.PUBLIC_CHAT_API_URL;
|
| 10 |
+
|
| 11 |
+
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
|
| 12 |
+
api: apiEndpoint,
|
| 13 |
+
});
|
| 14 |
+
|
| 15 |
+
return (
|
| 16 |
+
<div className="fixed bottom-6 right-6 z-50 font-sans">
|
| 17 |
+
{isOpen && (
|
| 18 |
+
<div className="mb-4 w-[380px] h-[550px] bg-white rounded-3xl shadow-[0_20px_50px_rgba(0,0,0,0.15)] border border-slate-100 flex flex-col overflow-hidden animate-in fade-in zoom-in duration-200">
|
| 19 |
+
<div className="bg-slate-900 p-5 text-white flex items-center justify-between">
|
| 20 |
+
<div className="flex items-center gap-3">
|
| 21 |
+
<div className="bg-blue-600 p-2 rounded-xl"><Bot size={20} /></div>
|
| 22 |
+
<div>
|
| 23 |
+
<p className="font-semibold text-sm">Inventory AI</p>
|
| 24 |
+
<p className="text-[10px] text-slate-400">Gemini 1.5 Flash Online</p>
|
| 25 |
+
</div>
|
| 26 |
+
</div>
|
| 27 |
+
<button onClick={() => setIsOpen(false)} className="opacity-50 hover:opacity-100"><X size={20} /></button>
|
| 28 |
+
</div>
|
| 29 |
+
|
| 30 |
+
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-slate-50">
|
| 31 |
+
{messages.map((m) => (
|
| 32 |
+
<div key={m.id} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
| 33 |
+
<div className={`max-w-[85%] px-4 py-2 rounded-2xl text-sm ${
|
| 34 |
+
m.role === 'user' ? 'bg-blue-600 text-white' : 'bg-white border border-slate-200 text-slate-800'
|
| 35 |
+
}`}>
|
| 36 |
+
{m.content}
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
))}
|
| 40 |
+
{isLoading && <Loader2 className="animate-spin text-blue-600 mx-auto" size={20} />}
|
| 41 |
+
</div>
|
| 42 |
+
|
| 43 |
+
<form onSubmit={handleSubmit} className="p-4 bg-white border-t border-slate-100 flex gap-2">
|
| 44 |
+
<input
|
| 45 |
+
className="flex-1 bg-slate-100 rounded-xl px-4 py-2 text-sm outline-none focus:ring-2 focus:ring-blue-500"
|
| 46 |
+
value={input}
|
| 47 |
+
placeholder="Спросите об оборудовании..."
|
| 48 |
+
onChange={handleInputChange}
|
| 49 |
+
/>
|
| 50 |
+
<button type="submit" className="bg-slate-900 text-white p-2 rounded-xl"><Send size={18} /></button>
|
| 51 |
+
</form>
|
| 52 |
+
</div>
|
| 53 |
+
)}
|
| 54 |
+
|
| 55 |
+
<button
|
| 56 |
+
onClick={() => setIsOpen(!isOpen)}
|
| 57 |
+
className="bg-slate-900 text-white w-16 h-16 rounded-full shadow-2xl flex items-center justify-center hover:scale-105 transition-transform"
|
| 58 |
+
>
|
| 59 |
+
{isOpen ? <X size={28} /> : <MessageCircle size={28} />}
|
| 60 |
+
</button>
|
| 61 |
+
</div>
|
| 62 |
+
);
|
| 63 |
+
}
|