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 (

⚖️ المساعد القانوني الذكي لنظام الأحوال الشخصية

{/*

اطرح أسئلة حول وثائقك القانونية

*/}
{messages.length > 0 && ( )}
{messages.length === 0 && (

مرحباً!

ابدأ بطرح سؤالا قانونيا...

)} {messages.map((msg, idx) => { const isAssistant = msg.role === 'assistant'; const contentIsString = typeof msg.content === 'string'; const renderContent = () => { if (isAssistant && contentIsString) { return ( {msg.content} ); } if (contentIsString) { return msg.content; } try { return JSON.stringify(msg.content, null, 2); } catch (err) { return 'محتوى غير قابل للعرض'; } }; return (
{msg.role === 'user' ? '👤 أنت' : '🤖 المساعد القانوني'}
{renderContent()}
{msg.sources && msg.sources.length > 0 && (
المصادر:
    {msg.sources.map((source, i) => (
  • {getDisplaySourceName(source)}
  • ))}
)}
); })} {loading && (
🤖 المساعد القانوني
جاري التفكير...
)}
setInput(e.target.value)} placeholder="اطرح سؤالاً قانونيا ..." disabled={loading} className="message-input" />
{(previewUrl || previewError || previewLoading) && (

معاينة المصدر

{previewFilename && {previewFilename}}
{previewLoading ? (

جاري تحميل المعاينة...

) : previewError ? (

{previewError}

) : (