import React, { useState, useEffect, useRef } from 'react'; import ReactMarkdown from 'react-markdown'; import { Send, Bot, User, ChevronLeft, LayoutGrid, GraduationCap as CapIcon, BookOpen, Upload, Settings, RefreshCw } from 'lucide-react'; const API_BASE = window.location.port === "5173" ? "http://localhost:8000/api" : "/api"; function App() { const [page, setPage] = useState('welcome'); const [curriculum, setCurriculum] = useState({}); const [selection, setSelection] = useState({ grade: '', subject: '', topic: '' }); const [messages, setMessages] = useState([]); const [inputText, setInputText] = useState(''); const [loading, setLoading] = useState(false); const [hintLevel, setHintLevel] = useState(1); const [status, setStatus] = useState('ACTIVE'); const [ingestStatus, setIngestStatus] = useState(''); const [uploadData, setUploadData] = useState({ grade: '', subject: '', file: null }); const messagesEndRef = useRef(null); useEffect(() => { fetchCurriculum(); }, []); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); const fetchCurriculum = async () => { try { const res = await fetch(`${API_BASE}/curriculum`); const data = await res.json(); setCurriculum(data); } catch (err) { console.error("Failed to fetch curriculum:", err); } }; const startSession = (grade, subject, topic) => { setSelection({ grade, subject, topic }); setMessages([{ role: 'assistant', content: `Hello! I'm your AI Learner for **${grade} ${subject}**. What would you like to explore today?` }]); setPage('chat'); }; const sendMessage = async (e) => { e.preventDefault(); if (!inputText.trim() || loading) return; const userMsg = { role: 'user', content: inputText }; const newMessages = [...messages, userMsg]; setMessages(newMessages); setInputText(''); setLoading(true); try { const res = await fetch(`${API_BASE}/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: newMessages, grade: selection.grade, subject: selection.subject, topic: selection.topic, hint_level: hintLevel, status: status }) }); if (!res.ok) { const errorData = await res.json().catch(() => ({})); throw new Error(errorData.detail || `Server error: ${res.status}`); } const data = await res.json(); setMessages([...newMessages, data.message]); setHintLevel(data.hint_level); setStatus(data.status); } catch (err) { console.error("Chat error:", err); setMessages([...newMessages, { role: 'assistant', content: `⚠️ **Error:** ${err.message}. Please check if the backend server is running and try again.` }]); } finally { setLoading(false); } }; const handleUpload = async (e) => { e.preventDefault(); if (!uploadData.file || !uploadData.grade || !uploadData.subject) return; setIngestStatus('Uploading...'); const formData = new FormData(); formData.append('file', uploadData.file); try { await fetch(`${API_BASE}/upload?grade=${uploadData.grade}&subject=${uploadData.subject}`, { method: 'POST', body: formData }); setIngestStatus('Processing...'); const res = await fetch(`${API_BASE}/ingest`, { method: 'POST' }); const result = await res.json(); setIngestStatus(result.message); fetchCurriculum(); } catch (err) { setIngestStatus('Error: ' + err.message); } }; // --- RENDERERS --- if (page === 'ingest') { return (
AI Learner Admin

Ingest New Knowledge

setUploadData({...uploadData, grade: e.target.value})} />
setUploadData({...uploadData, subject: e.target.value})} />
setUploadData({...uploadData, file: e.target.files[0]})} />
{ingestStatus &&
{ingestStatus}
}
); } if (page === 'welcome') { return (
AI Learner

Select your grade to begin

{Object.keys(curriculum).map(grade => (
{ setSelection({ ...selection, grade }); setPage('subject'); }}>

{grade}

))}
); } if (page === 'subject') { return (
AI Learner

Select Subject ({selection.grade})

{Object.keys(curriculum[selection.grade] || {}).map(subject => (
{ startSession(selection.grade, subject, 'General'); }}>

{subject}

))}
); } return (
AI Learner
Active Session
{selection.topic}
{selection.grade} • {selection.subject}

{selection.topic}

Powered by Gemini 3.1 Flash-Lite
Hint Level: {hintLevel}
{messages.map((msg, i) => (
{msg.content}
))} {loading && (
AI Learner is thinking...
)}
setInputText(e.target.value)} disabled={loading} />
); } export default App;