diff --git a/src/App.jsx b/src/App.jsx index 247c000..89dc58a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -331,6 +331,21 @@ export default function App() { // } // ] // }; + + // REAL Adaptive difficulty state + const [userPerformance, setUserPerformance] = useState({ + correctStreak: 0, + totalCorrect: 0, + totalAnswered: 0, + currentDifficulty: "Medium", + questionPool: { + Easy: [], + Medium: [], + Hard: [] + }, + currentQuestionIndex: 0 + }); + const [loading, setLoading] = useState(false); const [quiz, setQuiz] = useState([]); const [answers, setAnswers] = useState({}); @@ -365,33 +380,16 @@ export default function App() { localStorage.setItem("darkMode", JSON.stringify(isDarkMode)); }, [isDarkMode]); - const toggleDarkMode = () => { - setIsDarkMode(!isDarkMode); - }; - - // Fetch Quiz - async function fetchQuiz() { - setLoading(true); - setSubmitted(false); - setAnswers({}); - setShowStartScreen(false); - + // Generate questions for a specific difficulty + async function generateQuestions(difficulty, count = 5) { try { - // Use custom questions if available for the selected category - // if (customQuestions[selectedCategory]) { - // const questions = customQuestions[selectedCategory].slice(0, numQuestions); - // setQuiz(questions); - // setTimeLeft(questions.length * 30); - // setLoading(false); - // return; - // } - // Otherwise, use AI-generated questions const genAI = new GoogleGenerativeAI(import.meta.env.VITE_GEMINI_API_KEY); const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" }); + const prompt = ` - Generate ${numQuestions} multiple-choice questions focused on ${selectedCategory}, tailored for Indian government exam preparation (e.g., UPSC, SSC, or similar competitive exams). Ensure questions are exam-oriented: they should cover key topics, historical events, policies, figures, or concepts relevant to the category, with a focus on factual accuracy, analytical depth, and real-world application where appropriate. + Generate ${count} multiple-choice questions focused on ${selectedCategory} at ${difficulty} difficulty level. - Adhere to the selected difficulty level which is ${selectedDifficulty}: + DIFFICULTY SPECIFICS: - Easy: Basic recall of facts, straightforward questions with obvious distractors. - Medium: Require moderate understanding, including connections between concepts, with plausible distractors. - Hard: In-depth analysis, nuanced details, or application-based questions, with closely related distractors that test deep knowledge. @@ -402,6 +400,7 @@ export default function App() { - Options: Provide exactly 4 options per question. Distractors must be plausible and based on common misconceptions or related facts. - Answer: Must be factually correct and exactly match one option (case-sensitive, including spacing). - Explanation: Provide a detailed, educational explanation (2-4 sentences) citing why the answer is correct and why others are not, to aid learning. + - Focus on ${selectedCategory} topics Respond strictly in valid JSON array format (no extra text, code blocks, or markdown). Example: [ @@ -411,36 +410,183 @@ export default function App() { "answer": "Narendra Modi", "explanation": "Narendra Modi has been the Prime Minister of India since 2014, leading the BJP government. The other options are prominent politicians but not the current PM." } - ] + ]`; - Ensure the entire response is parseable as JSON.`; const result = await model.generateContent(prompt); let text = await result.response.text(); text = text.replace(/```json|```/g, "").trim(); - console.log("Raw AI response:", text); // Debug log - const questions = JSON.parse(text); - - // Validate and clean the data - const cleanedQuestions = questions.map((q) => ({ + return questions.map(q => ({ ...q, answer: q.answer?.trim(), - options: q.options?.map((opt) => opt?.trim()), + options: q.options?.map(opt => opt?.trim()), + difficulty: difficulty // Tag each question with its difficulty })); + } catch (err) { + console.error(`Error generating ${difficulty} questions:`, err); + return []; + } + } + + // Pre-load questions for ALL difficulty levels + async function preloadQuestionPools() { + setLoading(true); + + try { + // Generate questions for all difficulty levels in parallel + const [easyQuestions, mediumQuestions, hardQuestions] = await Promise.all([ + generateQuestions("Easy", 8), + generateQuestions("Medium", 8), + generateQuestions("Hard", 8) + ]); - console.log("Cleaned questions:", cleanedQuestions); // Debug log + setUserPerformance(prev => ({ + ...prev, + questionPool: { + Easy: easyQuestions, + Medium: mediumQuestions, + Hard: hardQuestions + }, + currentDifficulty: selectedDifficulty + })); - setQuiz(cleanedQuestions); - // Set timer based on number of questions (30 seconds per question) - setTimeLeft(cleanedQuestions.length * 30); + // Start with medium difficulty questions + setQuiz(mediumQuestions.slice(0, numQuestions)); + setTimeLeft(numQuestions * 30); + } catch (err) { - console.error("Error generating quiz:", err); - alert("Failed to generate quiz. Check console."); + console.error("Error preloading questions:", err); + alert("Failed to load questions. Please try again."); } setLoading(false); } + // Get next question based on current performance + const getNextQuestion = (currentPerformance) => { + const { questionPool, currentDifficulty, currentQuestionIndex } = currentPerformance; + + // If we have questions left in current difficulty pool + if (currentQuestionIndex < questionPool[currentDifficulty].length - 1) { + return questionPool[currentDifficulty][currentQuestionIndex]; + } + + // If we're out of questions in current difficulty, try to get from other pools + const availableQuestions = [ + ...questionPool[currentDifficulty], + ...questionPool.Medium, + ...questionPool.Easy, + ...questionPool.Hard + ].filter(q => !answers[Object.keys(answers).length]); // Not already answered + + return availableQuestions[0] || questionPool.Medium[0]; // Fallback + }; + + // REAL adaptive difficulty calculation + const calculateRealAdaptiveDifficulty = (performance) => { + const { correctStreak, totalCorrect, totalAnswered, currentDifficulty } = performance; + + if (totalAnswered < 2) return currentDifficulty; // Wait for some data + + const accuracy = totalCorrect / totalAnswered; + + // Adaptive logic: + if (currentDifficulty === "Easy" && correctStreak >= 3 && accuracy >= 0.7) { + return "Medium"; + } else if (currentDifficulty === "Medium") { + if (correctStreak >= 3 && accuracy >= 0.8) { + return "Hard"; + } else if (accuracy < 0.4) { + return "Easy"; + } + } else if (currentDifficulty === "Hard" && accuracy < 0.5) { + return "Medium"; + } + + return currentDifficulty; + }; + + // Handle option selection with REAL adaptation + function handleOptionSelect(qIndex, option) { + if (!submitted) { + const currentQuestion = quiz[qIndex]; + const isCorrect = option?.trim().toLowerCase() === currentQuestion.answer?.trim().toLowerCase(); + + setAnswers((prev) => ({ ...prev, [qIndex]: option })); + + // Update performance and potentially change difficulty + setUserPerformance(prev => { + const newTotalAnswered = prev.totalAnswered + 1; + const newTotalCorrect = prev.totalCorrect + (isCorrect ? 1 : 0); + const newCorrectStreak = isCorrect ? prev.correctStreak + 1 : 0; + const newQuestionIndex = prev.currentQuestionIndex + 1; + + const newPerformance = { + ...prev, + correctStreak: newCorrectStreak, + totalCorrect: newTotalCorrect, + totalAnswered: newTotalAnswered, + currentQuestionIndex: newQuestionIndex + }; + + // Check if we should change difficulty + const newDifficulty = calculateRealAdaptiveDifficulty(newPerformance); + + // If difficulty changed, update the quiz with new questions + if (newDifficulty !== prev.currentDifficulty) { + setTimeout(() => { + updateQuizWithNewDifficulty(newDifficulty); + }, 500); // Small delay for smooth transition + } + + return { + ...newPerformance, + currentDifficulty: newDifficulty + }; + }); + } + } + + // Update quiz when difficulty changes + const updateQuizWithNewDifficulty = (newDifficulty) => { + setUserPerformance(prev => { + const newQuestions = prev.questionPool[newDifficulty].slice(0, numQuestions); + + // Update the displayed quiz + setQuiz(newQuestions); + + // Reset answers for new questions (keep old ones for scoring) + setAnswers({}); + + return prev; + }); + }; + + const toggleDarkMode = () => { + setIsDarkMode(!isDarkMode); + }; + + // Fetch Quiz + async function fetchQuiz() { + setLoading(true); + setSubmitted(false); + setAnswers({}); + setShowStartScreen(false); + + // Reset performance tracking + setUserPerformance({ + correctStreak: 0, + totalCorrect: 0, + totalAnswered: 0, + currentDifficulty: selectedDifficulty, + questionPool: { Easy: [], Medium: [], Hard: [] }, + currentQuestionIndex: 0 + }); + + // Pre-load questions for all difficulty levels + await preloadQuestionPools(); + } + // Timer useEffect(() => { if (timeLeft > 0 && !submitted && quiz.length > 0) { @@ -450,10 +596,6 @@ export default function App() { if (timeLeft === 0 && quiz.length > 0 && !submitted) handleSubmit(); }, [timeLeft, submitted, quiz]); - function handleOptionSelect(qIndex, option) { - if (!submitted) setAnswers((prev) => ({ ...prev, [qIndex]: option })); - } - function handleSubmit() { setSubmitted(true); } @@ -486,6 +628,14 @@ export default function App() { setSubmitted(false); setTimeLeft(0); setShowStartScreen(true); + setUserPerformance({ + correctStreak: 0, + totalCorrect: 0, + totalAnswered: 0, + currentDifficulty: selectedDifficulty, + questionPool: { Easy: [], Medium: [], Hard: [] }, + currentQuestionIndex: 0 + }); } const score = submitted ? calculateScore() : null; @@ -503,6 +653,26 @@ export default function App() { const progress = calculateProgress(); + // Get difficulty badge color + const getDifficultyColor = (difficulty) => { + switch (difficulty) { + case "Easy": return "bg-green-500"; + case "Medium": return "bg-yellow-500"; + case "Hard": return "bg-red-500"; + default: return "bg-blue-500"; + } + }; + + // Get difficulty emoji + const getDifficultyEmoji = (difficulty) => { + switch (difficulty) { + case "Easy": return "😊"; + case "Medium": return "😐"; + case "Hard": return "🔥"; + default: return "🎯"; + } + }; + // Close mobile menu when clicking outside useEffect(() => { const handleClickOutside = (event) => { @@ -562,27 +732,7 @@ export default function App() { className="ml-2 p-2 rounded-lg bg-white/10 hover:bg-white/20 backdrop-blur-sm transition-all duration-300 border border-white/20 hover:border-white/30" aria-label="Toggle dark mode" > - {isDarkMode ? ( - - ) : ( - - )} + {isDarkMode ? "🌙" : "☀️"} @@ -593,27 +743,7 @@ export default function App() { className="p-2 rounded-lg bg-white/10 hover:bg-white/20 backdrop-blur-sm transition-all duration-300 border border-white/20 hover:border-white/30" aria-label="Toggle dark mode" > - {isDarkMode ? ( - - ) : ( - - )} + {isDarkMode ? "🌙" : "☀️"}
- Crafting {numQuestions} questions on {selectedCategory}... + Generating Easy, Medium & Hard questions...
- -{q.question}
+ Your journey: Started at {selectedDifficulty} • Reached:{" "} + + {userPerformance.currentDifficulty} + +