import { useState, useEffect, useRef } from "react"; const API_BASE = import.meta.env.VITE_API_URL || "http://localhost:8000/api"; const api = async (endpoint, method = "GET", body = null, token = null) => { const headers = { "Content-Type": "application/json" }; if (token) headers["Authorization"] = `Bearer ${token}`; const res = await fetch(`${API_BASE}${endpoint}`, { method, headers, body: body ? JSON.stringify(body) : null, }); if (!res.ok) { const err = await res.json(); throw new Error(err.detail || "API Error"); } return res.json(); }; // ── TRANSLATIONS ────────────────────────────────────────────────────────────── const TRANSLATIONS = { en: { tagline: "Your intelligent study assistant", signIn: "Sign in", createAccount: "Create account", username: "Username", email: "Email", password: "Password", processing: "Processing...", chatSubtitle: "Ask anything or explore your documents", quizSubtitle: "Test your knowledge", flashcardsSubtitle: "Study with smart cards", explainSubtitle: "Get clear explanations", uploadSubtitle: "Manage your course materials", profileSubtitle: "Your learning progress", generalChat: "General Chat", myDocs: "My Documents (RAG)", shiftEnter: "Shift+Enter for new line", chatPlaceholderRag: "Ask a question...", chatPlaceholder: "Type your message...", quizGenerator: "Quiz Generator", topic: "Topic", topicPlaceholderQuiz: "Enter a topic...", questions: "Questions", difficulty: "Difficulty", easy: "Easy", medium: "Medium", hard: "Hard", startQuiz: "Start Quiz", generating: "Generating...", result: "Result", correct: "Correct", wrong: "Wrong", time: "Time", newQuiz: "New Quiz", excellentWork: "Excellent work", goodEffort: "Good effort", keepStudying: "Keep studying", explanation: "Explanation", previous: "Previous", next: "Next", submit: "Submit", flashcardGenerator: "Flashcard Generator", topicPlaceholderFC: "Enter a topic...", generateFlashcards: "Generate Flashcards", noFlashcards: "No flashcards generated.", newTopic: "New Topic", question: "Question", answer: "Answer", clickReveal: "Click to reveal answer", clickQuestion: "Click to see question", conceptExplainer: "Concept Explainer", concept: "Concept", topicPlaceholderExplain: "Enter a concept...", level: "Level", beginner: "Beginner", intermediate: "Intermediate", advanced: "Advanced", explain: "Explain", output: "Output", documentManager: "Document Manager", subject: "Subject", indexingDoc: "Indexing document...", chunkingNote: "Chunking + embedding into ChromaDB", dragOrClick: "Drag a file or click to upload", dropping: "Drop your file here", indexedDocs: "Indexed Documents", noDocsYet: "No documents yet", uploadNote: "Upload your course materials to use RAG in Chat", files: "files", chunks: "chunks", delete: "Delete", unsupportedFormat: "Unsupported format. Accepted: PDF, TXT, DOCX", sessions: "Sessions", quizzes: "Quizzes", average: "Average", best: "Best", quizHistory: "Quiz History", memberSince: "Member since", dayStreak: "Day streak", failedProfile: "Failed to load profile.", out: "Out", learning: "Learning", resources: "Resources", noQuestionsGenerated: "No questions generated.", nav: { chat: "Chat", quiz: "Quiz", flashcards: "Flashcards", explain: "Explain", upload: "Documents", profile: "Profile" }, }, fr: { tagline: "Votre assistant d'étude intelligent", signIn: "Se connecter", createAccount: "Créer un compte", username: "Nom d'utilisateur", email: "E-mail", password: "Mot de passe", processing: "Traitement...", chatSubtitle: "Posez vos questions ou explorez vos documents", quizSubtitle: "Testez vos connaissances", flashcardsSubtitle: "Étudiez avec des fiches intelligentes", explainSubtitle: "Obtenez des explications claires", uploadSubtitle: "Gérez vos supports de cours", profileSubtitle: "Votre progression d'apprentissage", generalChat: "Chat Général", myDocs: "Mes Documents (RAG)", shiftEnter: "Maj+Entrée pour nouvelle ligne", chatPlaceholderRag: "Posez une question...", chatPlaceholder: "Tapez votre message...", quizGenerator: "Générateur de Quiz", topic: "Sujet", topicPlaceholderQuiz: "Entrez un sujet...", questions: "Questions", difficulty: "Difficulté", easy: "Facile", medium: "Moyen", hard: "Difficile", startQuiz: "Démarrer le Quiz", generating: "Génération...", result: "Résultat", correct: "Correct", wrong: "Faux", time: "Temps", newQuiz: "Nouveau Quiz", excellentWork: "Excellent travail", goodEffort: "Bon effort", keepStudying: "Continuez à étudier", explanation: "Explication", previous: "Précédent", next: "Suivant", submit: "Soumettre", flashcardGenerator: "Générateur de Fiches", topicPlaceholderFC: "Entrez un sujet...", generateFlashcards: "Générer les Fiches", noFlashcards: "Aucune fiche générée.", newTopic: "Nouveau Sujet", question: "Question", answer: "Réponse", clickReveal: "Cliquer pour révéler la réponse", clickQuestion: "Cliquer pour voir la question", conceptExplainer: "Explication de Concept", concept: "Concept", topicPlaceholderExplain: "Entrez un concept...", level: "Niveau", beginner: "Débutant", intermediate: "Intermédiaire", advanced: "Avancé", explain: "Expliquer", output: "Résultat", documentManager: "Gestionnaire de Documents", subject: "Matière", indexingDoc: "Indexation du document...", chunkingNote: "Découpage + intégration dans ChromaDB", dragOrClick: "Glissez un fichier ou cliquez pour télécharger", dropping: "Déposez votre fichier ici", indexedDocs: "Documents Indexés", noDocsYet: "Aucun document pour l'instant", uploadNote: "Téléchargez vos supports de cours pour utiliser le RAG dans le Chat", files: "fichiers", chunks: "segments", delete: "Supprimer", unsupportedFormat: "Format non pris en charge. Acceptés : PDF, TXT, DOCX", sessions: "Sessions", quizzes: "Quiz", average: "Moyenne", best: "Meilleur", quizHistory: "Historique des Quiz", memberSince: "Membre depuis", dayStreak: "Jours consécutifs", failedProfile: "Échec du chargement du profil.", out: "Déco", learning: "Apprentissage", resources: "Ressources", noQuestionsGenerated: "Aucune question générée.", nav: { chat: "Chat", quiz: "Quiz", flashcards: "Fiches", explain: "Expliquer", upload: "Documents", profile: "Profil" }, }, }; const GlobalStyle = () => ( ); const C = { bg: "#F7F5F0", surface: "#FFFFFF", card: "#FFFFFF", sidebar: "#1C1917", sidebarHover: "#292524", sidebarActive: "#292524", sidebarText: "#A8A29E", sidebarMuted: "#57534E", border: "#E7E5E0", borderStrong: "#D6D3CD", accent: "#C2410C", accentMid: "#EA580C", accentLight: "#FEF3EC", accentBorder: "#FDDCCA", text: "#1C1917", secondary: "#78716C", muted: "#A8A29E", green: "#16A34A", yellow: "#D97706", red: "#DC2626", white: "#FFFFFF", }; // ── LANGUAGE SWITCHER ───────────────────────────────────────────────────────── const LangSwitcher = ({ lang, setLang }) => (
{["en", "fr"].map(l => ( ))}
); // ── SHARED COMPONENTS ───────────────────────────────────────────────────────── const SectionBar = ({ label }) => (

{label}

); const Tag = ({ children, color = C.accent }) => ( {children} ); const Spinner = () => ( ); // ── AUTH ────────────────────────────────────────────────────────────────────── const AuthPage = ({ onLogin, t, lang, setLang }) => { const [mode, setMode] = useState("login"); const [form, setForm] = useState({ username: "", email: "", password: "" }); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const handle = async () => { setError(""); setLoading(true); try { const endpoint = mode === "login" ? "/auth/login" : "/auth/register"; const body = mode === "login" ? { email: form.email, password: form.password } : { username: form.username, email: form.email, password: form.password }; const data = await api(endpoint, "POST", body); onLogin(data); } catch (e) { setError(e.message); } setLoading(false); }; return ( <>
{/* Overlay */}
{/* Lang switcher top-right */}
{["en", "fr"].map(l => ( ))}
{/* Card */}
PaperBrain

{t.tagline}

{["login", "register"].map(tab => ( ))}
{mode === "register" && (
{t.username}
setForm({ ...form, username: e.target.value })} />
)}
{t.email}
setForm({ ...form, email: e.target.value })} />
{t.password}
setForm({ ...form, password: e.target.value })} onKeyDown={e => e.key === "Enter" && handle()} />
{error && (
{error}
)}
); }; // ── CHAT ────────────────────────────────────────────────────────────────────── const ChatPage = ({ token, username, t }) => { const [messages, setMessages] = useState([ { role: "ai", text: `Hello ${username}.\n\n${t.generalChat}: open questions\nRAG Mode: answers from your uploaded documents` } ]); const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); const [mode, setMode] = useState("chat"); const endRef = useRef(null); const textareaRef = useRef(null); useEffect(() => { endRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages, loading]); const handleInput = (e) => { setInput(e.target.value); e.target.style.height = "auto"; e.target.style.height = Math.min(e.target.scrollHeight, 120) + "px"; }; const send = async () => { if (!input.trim() || loading) return; const q = input.trim(); setInput(""); if (textareaRef.current) textareaRef.current.style.height = "auto"; setLoading(true); setMessages(m => [...m, { role: "user", text: q }]); try { const endpoint = mode === "rag" ? "/rag-qa" : "/chat"; const data = await api(endpoint, "POST", { query: q, user_id: username }, token); const sources = data.sources?.length > 0 ? `\n\nSources: ${data.sources.join(", ")}` : ""; setMessages(m => [...m, { role: "ai", text: (data.answer || "No response.") + sources }]); } catch (e) { setMessages(m => [...m, { role: "ai", text: "Error: " + e.message }]); } setLoading(false); }; const initials = username?.[0]?.toUpperCase() || "U"; return (
Mode: {[{ id: "chat", label: t.generalChat }, { id: "rag", label: t.myDocs }].map(m => ( ))}
{messages.map((m, i) => (
{m.role === "ai" && (
P
)}
{m.text}
{m.role === "user" && (
{initials}
)}
))} {loading && (
P
{[0, 1, 2].map(i => )}
)}