anycoder-eea07d51 / components /ChatInterface.jsx
mohammadSaber26's picture
Upload components/ChatInterface.jsx with huggingface_hub
e378268 verified
'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;