Bienvenue sur Kibali AI
Assistant IA expert du Gabon avec mémoire contextuelle et analyse documentaire avancée.
{!user && ({m.content}
{m.timestamp && (GABONESE SOVEREIGN ARTIFICIAL INTELLIGENCE
import React, { useState, useEffect, useRef } from 'react'; import axios from 'axios'; import { Send, FileText, Upload, Globe, MapPin, Loader2, Trash2, Image as ImageIcon, Search, Brain, ChevronDown, ChevronUp, User, Database, Clock, TrendingUp, AlertCircle, CheckCircle, Zap, LogOut } from 'lucide-react'; import { initializeApp } from 'firebase/app'; import { getAuth, GoogleAuthProvider, signInWithRedirect, signOut, onAuthStateChanged, getRedirectResult } from 'firebase/auth'; // Configuration Firebase const firebaseConfig = { apiKey: import.meta.env.VITE_FIREBASE_API_KEY || "AIzaSyCel4qvAtZomh4aQOCArCLIYSYmeZ4Qbig", authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN || "sevenstatut.firebaseapp.com", databaseURL: import.meta.env.VITE_FIREBASE_DATABASE_URL || "https://sevenstatut-default-rtdb.firebaseio.com", projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID || "sevenstatut", storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET || "sevenstatut.firebasestorage.app", messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID || "838268990346", appId: import.meta.env.VITE_FIREBASE_APP_ID || "1:838268990346:web:dba8702c39e82981704e73" }; // Initialiser Firebase const app = initializeApp(firebaseConfig); const auth = getAuth(app); const googleProvider = new GoogleAuthProvider(); const API_BASE = "https://belikanm-kibali-api.hf.space"; const LOGO_PATH = "/kibali_logo.svg"; function App() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); const [uploading, setUploading] = useState(false); const [status, setStatus] = useState({ doc_chunks: 0, memory_entries: 0, current_subject: null, subject_message_count: 0, torch_cuda_available: false, status: 'unknown' }); const [showThinking, setShowThinking] = useState({}); const [uploadProgress, setUploadProgress] = useState(null); const [user, setUser] = useState(null); const [authLoading, setAuthLoading] = useState(true); const scrollRef = useRef(null); const pollingInterval = useRef(null); // Gestion de l'authentification useEffect(() => { // Vérifier le résultat de la redirection getRedirectResult(auth) .then((result) => { if (result) { setUser(result.user); } setAuthLoading(false); }) .catch((error) => { console.error("Erreur lors de la redirection:", error); setAuthLoading(false); }); // Écouter les changements d'état d'authentification const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => { setUser(firebaseUser); if (!firebaseUser) { setAuthLoading(false); } }); return () => unsubscribe(); }, []); // Récupération du statut du backend au démarrage et toutes les 10 secondes useEffect(() => { fetchStatus(); pollingInterval.current = setInterval(fetchStatus, 10000); return () => { if (pollingInterval.current) { clearInterval(pollingInterval.current); } }; }, []); const fetchStatus = async () => { try { const response = await axios.get(`${API_BASE}/status`, { timeout: 5000 }); setStatus(response.data); } catch (error) { console.error("Erreur récupération status:", error); } }; // Connexion Google avec redirection const handleGoogleLogin = async () => { try { await signInWithRedirect(auth, googleProvider); } catch (error) { console.error("Erreur de connexion Google:", error); alert(`Erreur de connexion: ${error.message}`); } }; // Déconnexion const handleLogout = async () => { try { await signOut(auth); setUser(null); } catch (error) { console.error("Erreur de déconnexion:", error); } }; // Auto-scroll vers le bas useEffect(() => { scrollRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages, loading]); // Envoi du message const handleSend = async () => { if (!input.trim() || loading) return; const userMsg = { role: "user", content: input.trim(), userPhoto: user?.photoURL || null, userName: user?.displayName || "Utilisateur" }; setMessages(prev => [...prev, userMsg]); setInput(""); setLoading(true); try { const response = await axios.post(`${API_BASE}/chat`, { messages: [...messages, userMsg], latitude: 0.4061, longitude: 9.4673, city: "Libreville", thinking_mode: true }, { timeout: 120000 }); const aiResponse = response.data.response || "Réponse reçue du serveur."; const aiImages = response.data.images || []; const contextInfo = response.data.context_info || {}; setMessages(prev => [...prev, { role: "assistant", content: aiResponse, images: aiImages, context_info: contextInfo, timestamp: new Date().toISOString() }]); fetchStatus(); } catch (error) { console.error("Erreur lors de l'appel au backend:", error); let errorMsg = "Erreur : impossible de contacter le serveur IA."; if (error.code === 'ERR_NETWORK') { errorMsg = "⚠️ Serveur injoignable. Vérifiez que votre backend est lancé."; } else if (error.code === 'ECONNABORTED') { errorMsg = "⏱️ Timeout : la requête a pris trop de temps."; } else if (error.response?.status === 400) { errorMsg = `❌ Erreur de requête : ${error.response.data.detail || 'Format invalide'}`; } else if (error.response?.status === 500) { errorMsg = "🔥 Erreur serveur interne. Vérifiez les logs du backend."; } else if (error.response?.data?.detail) { errorMsg = `Erreur serveur : ${error.response.data.detail}`; } setMessages(prev => [...prev, { role: "assistant", content: errorMsg, error: true, timestamp: new Date().toISOString() }]); } finally { setLoading(false); } }; // Upload de fichiers PDF const handleFileUpload = async (e) => { const files = Array.from(e.target.files); if (!files.length) return; setUploading(true); setUploadProgress({ current: 0, total: files.length }); const formData = new FormData(); files.forEach(file => formData.append('files', file)); try { const res = await axios.post(`${API_BASE}/upload`, formData, { headers: { 'Content-Type': 'multipart/form-data' }, timeout: 180000, onUploadProgress: (progressEvent) => { const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total); setUploadProgress(prev => ({ ...prev, percent: percentCompleted })); } }); const chunksAdded = res.data.chunks_added || 0; const filesProcessed = res.data.files_processed || 0; const totalChunks = res.data.total_doc_chunks || 0; setStatus(prev => ({ ...prev, doc_chunks: totalChunks })); setMessages(prev => [...prev, { role: "assistant", content: `✅ **Import réussi !**\n\n📄 ${filesProcessed} document(s) traité(s)\n📊 ${chunksAdded} nouveaux segments ajoutés\n💾 Total en base : **${totalChunks} chunks**\n\nVous pouvez maintenant poser des questions sur ces documents !`, success: true, timestamp: new Date().toISOString() }]); fetchStatus(); } catch (err) { console.error("Erreur upload:", err); let errorMsg = "❌ Échec de l'import des documents."; if (err.code === 'ECONNABORTED') { errorMsg += " Timeout : les fichiers sont peut-être trop volumineux."; } else if (err.response?.data?.detail) { errorMsg += ` Détails : ${err.response.data.detail}`; } setMessages(prev => [...prev, { role: "assistant", content: errorMsg, error: true, timestamp: new Date().toISOString() }]); } finally { setUploading(false); setUploadProgress(null); e.target.value = ''; } }; // Réinitialisation complète (chat + mémoire) const handleReset = async () => { if (!window.confirm("Voulez-vous réinitialiser la conversation ET effacer la mémoire du modèle ?")) return; try { await axios.post(`${API_BASE}/clear-memory`, {}, { timeout: 5000 }); setMessages([]); fetchStatus(); setMessages([{ role: "assistant", content: "✅ Conversation et mémoire réinitialisées avec succès.", success: true, timestamp: new Date().toISOString() }]); } catch (error) { console.error("Erreur lors de la réinitialisation:", error); setMessages([{ role: "assistant", content: "⚠️ Chat réinitialisé, mais impossible de contacter le serveur pour effacer la mémoire.", error: true, timestamp: new Date().toISOString() }]); } }; const toggleThinking = (msgIndex) => { setShowThinking(prev => ({ ...prev, [msgIndex]: !prev[msgIndex] })); }; const formatTimeAgo = (timestamp) => { if (!timestamp) return ""; const seconds = Math.floor((new Date() - new Date(timestamp)) / 1000); if (seconds < 60) return "à l'instant"; if (seconds < 3600) return `il y a ${Math.floor(seconds / 60)}m`; if (seconds < 86400) return `il y a ${Math.floor(seconds / 3600)}h`; return `il y a ${Math.floor(seconds / 86400)}j`; }; // Styles centralisés const styles = { container: { display: 'flex', height: '100vh', backgroundColor: '#020617', color: '#f8fafc', fontFamily: 'system-ui, -apple-system, sans-serif' }, sidebar: { width: '340px', backgroundColor: '#0f172a', borderRight: '1px solid #1e293b', display: 'flex', flexDirection: 'column', overflowY: 'auto' }, main: { flex: 1, display: 'flex', flexDirection: 'column', position: 'relative', background: 'radial-gradient(circle at 50% 0%, #1e293b 0%, #020617 100%)' }, messageArea: { flex: 1, overflowY: 'auto', padding: '2rem 10% 2rem 10%' }, userBubble: { backgroundColor: '#059669', color: 'white', padding: '1rem 1.25rem', borderRadius: '1.25rem 1.25rem 0.25rem 1.25rem', boxShadow: '0 4px 15px rgba(5, 150, 105, 0.2)' }, aiBubble: { backgroundColor: '#1e293b', border: '1px solid #334155', color: '#f1f5f9', padding: '1rem 1.25rem', borderRadius: '1.25rem 1.25rem 1.25rem 0.25rem', boxShadow: '0 10px 30px rgba(0,0,0,0.5)' }, successBubble: { backgroundColor: '#064e3b', border: '1px solid #059669', color: '#d1fae5' }, errorBubble: { backgroundColor: '#7f1d1d', border: '1px solid #991b1b', color: '#fca5a5' }, inputContainer: { padding: '2rem', background: 'linear-gradient(to top, #020617 70%, transparent)' }, inputWrapper: { display: 'flex', alignItems: 'center', backgroundColor: 'rgba(30, 41, 59, 0.7)', backdropFilter: 'blur(20px)', border: '1px solid #334155', borderRadius: '1.5rem', padding: '0.6rem 1.2rem', maxWidth: '900px', margin: '0 auto' }, inputField: { flex: 1, background: 'transparent', border: 'none', color: 'white', padding: '0.8rem', outline: 'none', fontSize: '1rem' }, sendBtn: { backgroundColor: '#10b981', color: 'white', border: 'none', borderRadius: '1rem', width: '45px', height: '45px', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', transition: 'all 0.2s' }, toolBadge: { display: 'flex', alignItems: 'center', gap: '6px', fontSize: '11px', backgroundColor: '#020617', padding: '6px 12px', borderRadius: '10px', border: '1px solid #1e293b', color: '#94a3b8' }, statCard: { backgroundColor: 'rgba(2, 6, 23, 0.5)', borderRadius: '1rem', padding: '1rem', border: '1px solid #1e293b', marginBottom: '0.8rem' }, googleBtn: { width: '100%', padding: '12px 16px', backgroundColor: '#0f172a', border: '1px solid #334155', borderRadius: '12px', color: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '10px', cursor: 'pointer', fontWeight: '600', fontSize: '14px', transition: 'all 0.2s', marginBottom: '1rem' }, userProfile: { display: 'flex', alignItems: 'center', gap: '12px', padding: '12px 16px', backgroundColor: 'rgba(2, 6, 23, 0.5)', borderRadius: '12px', border: '1px solid #334155', marginBottom: '1rem' } }; return (
Assistant IA expert du Gabon avec mémoire contextuelle et analyse documentaire avancée.
{!user && ({m.content}
{m.timestamp && (GABONESE SOVEREIGN ARTIFICIAL INTELLIGENCE