Spaces:
Build error
Build error
| 'use client'; | |
| import React, { useState, useRef, useEffect } from 'react'; | |
| import { Send, Bot, User, AlertTriangle, Code, ChevronDown } from 'lucide-react'; | |
| const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1/chat'; | |
| const USER_ID = 1; | |
| const ChatInterface = () => { | |
| const [messages, setMessages] = useState([]); | |
| const [input, setInput] = useState(''); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const messagesEndRef = useRef(null); | |
| const scrollToBottom = () => { | |
| messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); | |
| }; | |
| useEffect(() => { | |
| scrollToBottom(); | |
| }, [messages]); | |
| const addMessage = (content, type = 'bot') => { | |
| setMessages((prev) => [...prev, { content, type }]); | |
| }; | |
| const handleSend = async () => { | |
| const text = input.trim(); | |
| if (!text) return; | |
| addMessage(text, 'user'); | |
| setInput(''); | |
| setIsLoading(true); | |
| try { | |
| const res = await fetch(API_URL, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| message: text, | |
| user_id: USER_ID, | |
| }), | |
| }); | |
| if (!res.ok) throw new Error(`HTTP ${res.status}`); | |
| const data = await res.json(); | |
| renderResponse(data); | |
| } catch (e) { | |
| addMessage(`❌ خطا در ارسال پیام: ${e.message}`, 'bot'); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| const renderResponse = (data) => { | |
| let html = ''; | |
| // متن اصلی | |
| if (data.type === 'text') { | |
| html += `<p>${data.data.message}</p>`; | |
| } | |
| // نتیجه محصول | |
| if (data.type === 'product_list') { | |
| html += `<p><b>نتیجه:</b> ${data.data.message}</p>`; | |
| html += `<p>تعداد: ${data.data.total_count}</p>`; | |
| data.data.products.forEach((p) => { | |
| html += ` | |
| <div class="product-card"> | |
| <b>${p.name}</b><br/> | |
| قیمت: ${p.formatted_price} تومان<br/> | |
| وضعیت: ${p.stock ? 'موجود' : 'ناموجود'}<br/> | |
| برند: ${p.brand || '-'} | شهر: ${p.city || '-'} | |
| </div> | |
| `; | |
| }); | |
| } | |
| // 🔴 اگر ابهام وجود داشت | |
| if (data.debug && data.debug.is_ambiguous && data.debug.clarification_question) { | |
| html += `<p class="message-clarification">❗ ابهام تشخیص داده شد: ${data.debug.clarification_question}</p>`; | |
| } | |
| // 🔍 JSON فیلترها (همیشه) | |
| if (data.debug) { | |
| html += ` | |
| <details> | |
| <summary className="cursor-pointer text-gray-500 hover:text-gray-700 mb-2"> | |
| <ChevronDown className="inline w-4 h-4 mr-1" /> 🧪 فیلترهای استخراجشده توسط LLM | |
| </summary> | |
| <pre className="debug-panel">{JSON.stringify(data.debug, null, 2)}</pre> | |
| </details> | |
| `; | |
| } | |
| addMessage(html, 'bot'); | |
| }; | |
| return ( | |
| <div className="min-h-screen flex flex-col items-center justify-center bg-gray-100 p-4"> | |
| <div className="chat-container"> | |
| <header className="bg-brand-700 text-white p-4 flex items-center justify-between"> | |
| <div className="flex items-center gap-2"> | |
| <Bot className="w-6 h-6" /> | |
| <h1 className="text-xl font-bold">🧠 Enterprise Search Chat</h1> | |
| </div> | |
| <a | |
| href="https://huggingface.co/spaces/akhaliq/anycoder" | |
| target="_blank" | |
| rel="noreferrer" | |
| className="flex items-center gap-1 text-brand-100 hover:text-white transition-colors text-sm" | |
| > | |
| Built with anycoder | |
| </a> | |
| </header> | |
| <div className="chat-messages"> | |
| {messages.length === 0 && ( | |
| <div className="flex flex-col items-center justify-center h-full text-gray-400 space-y-2"> | |
| <Bot className="w-12 h-12" /> | |
| <p>چطور میتوانم کمکتان کنم؟</p> | |
| </div> | |
| )} | |
| {messages.map((msg, idx) => ( | |
| <div key={idx} className={msg.type === 'user' ? 'message-user' : 'message-bot'}> | |
| <div className="message-content"> | |
| {msg.type === 'user' ? ( | |
| <User className="w-4 h-4 inline-block mb-1" /> | |
| ) : ( | |
| <Bot className="w-4 h-4 inline-block mb-1" /> | |
| )} | |
| <div className="whitespace-pre-wrap"> | |
| {msg.content} | |
| </div> | |
| </div> | |
| </div> | |
| ))} | |
| {isLoading && ( | |
| <div className="flex justify-start"> | |
| <div className="message-content bg-white border border-gray-200 rounded-bl-none"> | |
| <div className="flex items-center gap-2"> | |
| <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" /> | |
| <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-100" /> | |
| <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-200" /> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| <div className="p-4 bg-white border-t border-gray-200"> | |
| <div className="flex gap-2"> | |
| <input | |
| type="text" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyPress={(e) => e.key === 'Enter' && handleSend()} | |
| placeholder="مثلاً: برند اپکس بالای 5 میلیون" | |
| disabled={isLoading} | |
| className="flex-1 px-4 py-3 rounded-xl border border-gray-300 focus:outline-none focus:ring-2 focus:ring-brand-500 disabled:bg-gray-100 disabled:cursor-not-allowed" | |
| /> | |
| <button | |
| onClick={handleSend} | |
| disabled={isLoading || !input.trim()} | |
| className="px-6 py-3 bg-brand-600 text-white rounded-xl hover:bg-brand-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-all flex items-center gap-2" | |
| > | |
| <Send className="w-5 h-5" /> | |
| <span className="hidden sm:inline">ارسال</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default ChatInterface; |