import React, { useState, useRef, useEffect } from 'react'; import './App.css'; import axios from 'axios'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; // Use relative URL for production (Hugging Face Spaces), absolute for local dev const API_URL = process.env.REACT_APP_API_URL || (process.env.NODE_ENV === 'production' ? '/api' : 'http://localhost:8000'); const DOCS_URL = process.env.REACT_APP_DOCS_URL || `${API_URL}/documents`; function App() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const [previewUrl, setPreviewUrl] = useState(null); const [previewFilename, setPreviewFilename] = useState(''); const [previewError, setPreviewError] = useState(null); const [previewLoading, setPreviewLoading] = useState(false); const messagesEndRef = useRef(null); const previewUrlRef = useRef(null); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { scrollToBottom(); }, [messages]); useEffect(() => { return () => { if (previewUrlRef.current) { URL.revokeObjectURL(previewUrlRef.current); } }; }, []); const handleSend = async (e) => { e.preventDefault(); if (!input.trim() || loading) return; // Use unique IDs to prevent collision const baseTime = Date.now(); const userMessageId = baseTime; const assistantMessageId = baseTime + 1; // Ensure different ID const userMessage = { id: userMessageId, role: 'user', content: input }; setMessages(prev => [...prev, userMessage]); setInput(''); setLoading(true); try { const response = await axios.post(`${API_URL}/ask`, { question: input }); const assistantMessage = { id: assistantMessageId, role: 'assistant', content: response.data.answer, sources: response.data.sources || [] }; setMessages(prev => [...prev, assistantMessage]); } catch (error) { const errorMessage = { id: assistantMessageId, role: 'assistant', content: error.response?.data?.detail || error.message || 'عذراً، حدث خطأ. يرجى المحاولة مرة أخرى.', error: true }; setMessages(prev => [...prev, errorMessage]); } finally { setLoading(false); } }; const handleClearHistory = async () => { if (window.confirm('هل أنت متأكد من أنك تريد مسح سجل المحادثة؟')) { try { await axios.post(`${API_URL}/clear-history`); setMessages([]); // Clear local messages state } catch (error) { console.error('Error clearing history:', error); alert('فشل مسح السجل. يرجى المحاولة مرة أخرى.'); } } }; const handleDownload = (source) => { if (!source) return; const filename = source.split('/').pop() || source; const url = `${DOCS_URL}/${encodeURIComponent(filename)}?mode=download`; window.open(url, '_blank'); }; const handleClosePreview = () => { if (previewUrlRef.current) { URL.revokeObjectURL(previewUrlRef.current); previewUrlRef.current = null; } setPreviewUrl(null); setPreviewError(null); setPreviewFilename(''); setPreviewLoading(false); }; const getDisplaySourceName = (source) => { if (!source) return ''; const fullName = source.split('/').pop() || source; const lastDot = fullName.lastIndexOf('.'); if (lastDot > 0) { return fullName.substring(0, lastDot); } return fullName; }; const handleSourceClick = async (source) => { if (!source) return; const filename = source.split('/').pop() || source; const extension = filename.split('.').pop()?.toLowerCase(); console.log('[Preview] Requesting preview for:', filename); setPreviewFilename(filename); setPreviewError(null); setPreviewLoading(true); if (previewUrlRef.current) { URL.revokeObjectURL(previewUrlRef.current); previewUrlRef.current = null; } setPreviewUrl(null); if (extension !== 'pdf') { const errorMsg = 'المعاينة متاحة فقط لملفات PDF.'; console.error('[Preview] Error:', errorMsg); setPreviewError(errorMsg); setPreviewLoading(false); return; } try { const url = `${DOCS_URL}/${encodeURIComponent(filename)}?mode=preview`; console.log('[Preview] Requesting URL:', url); const response = await axios.get(url, { responseType: 'blob', timeout: 30000 // 30 second timeout }); console.log('[Preview] Response received, status:', response.status, 'size:', response.data.size); if (!response.data || response.data.size === 0) { throw new Error('Received empty file'); } const blob = new Blob([response.data], { type: 'application/pdf' }); const objectUrl = URL.createObjectURL(blob); previewUrlRef.current = objectUrl; setPreviewUrl(objectUrl); console.log('[Preview] Successfully created object URL'); } catch (error) { console.error('[Preview] Error details:', { message: error.message, response: error.response?.data, status: error.response?.status, statusText: error.response?.statusText, url: error.config?.url }); let errorMsg = 'تعذر تحميل المعاينة.'; if (error.response?.data?.detail) { errorMsg = `خطأ: ${error.response.data.detail}`; } else if (error.response?.status === 404) { errorMsg = 'الملف غير موجود.'; } else if (error.response?.status === 403) { errorMsg = 'غير مسموح بالوصول إلى هذا الملف.'; } else if (error.message) { errorMsg = `خطأ: ${error.message}`; } setPreviewError(errorMsg); } finally { setPreviewLoading(false); } }; return (
اطرح أسئلة حول وثائقك القانونية
*/}ابدأ بطرح سؤالا قانونيا...
جاري تحميل المعاينة...
) : previewError ? ({previewError}
) : ( )}