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 ? "🌙" : "☀️"} @@ -772,23 +906,8 @@ export default function App() { Generating Your Quiz

- Crafting {numQuestions} questions on {selectedCategory}... + Generating Easy, Medium & Hard questions...

- -
-
-
-
-
@@ -801,15 +920,34 @@ export default function App() {

{selectedCategory} Quiz

+
- {selectedDifficulty} • {quiz.length} Questions + {getDifficultyEmoji(userPerformance.currentDifficulty)} {userPerformance.currentDifficulty} • {quiz.length} Questions + {/* REAL Adaptive difficulty indicator */} + + 🔄 Live Adaptive: {userPerformance.currentDifficulty} + + + {/* Performance stats */} + + 🔥 Streak: {userPerformance.correctStreak} • ✅ {userPerformance.totalCorrect}/{userPerformance.totalAnswered} + +
+ + {/* NEW: Difficulty progression info */} + {userPerformance.totalAnswered > 0 && ( +
+ {userPerformance.currentDifficulty === "Easy" && "Get 3+ correct in a row to advance to Medium!"} + {userPerformance.currentDifficulty === "Medium" && userPerformance.correctStreak >= 2 && "Keep going! 1 more correct for Hard level!"} + {userPerformance.currentDifficulty === "Hard" && "You're at the highest level! Maintain >50% accuracy."} +
+ )}
⏱️ - {Math.floor(timeLeft / 60)}: - {(timeLeft % 60).toString().padStart(2, "0")} + {Math.floor(timeLeft / 60)}:{(timeLeft % 60).toString().padStart(2, "0")}
@@ -817,6 +955,9 @@ export default function App() {
Progress: {progress.answered} of {progress.total} questions answered + {userPerformance.totalAnswered > 0 && ( + • Accuracy: {Math.round((userPerformance.totalCorrect / userPerformance.totalAnswered) * 100)}% + )} {progress.percentage}%
@@ -828,11 +969,17 @@ export default function App() { -
+
{quiz.map((q, idx) => (
- Q{idx + 1} +
+ Q{idx + 1} + {/* Show actual question difficulty */} + + {q.difficulty} + +

{q.question}

@@ -871,6 +1018,12 @@ export default function App() {
🎉

Quiz Completed!

+

+ Your journey: Started at {selectedDifficulty} • Reached:{" "} + + {userPerformance.currentDifficulty} + +

@@ -890,35 +1043,37 @@ export default function App() { {score.total - score.correct} +
+
+ Best Streak + {userPerformance.correctStreak}
- Total - {score.total} + Final Level + + {userPerformance.currentDifficulty} +
-
+

📝 Answer Review

{quiz.map((q, idx) => { const userAnswer = answers[idx]; const isCorrect = userAnswer === q.answer; return ( -
+
- Q{idx + 1} - +
+ Q{idx + 1} + + {q.difficulty} + +
+ {isCorrect ? "✅" : "❌"}
@@ -928,11 +1083,7 @@ export default function App() {
Your Answer: - + {userAnswer || "Not answered"}
diff --git a/src/index.css b/src/index.css index 5f0c28b..91244f8 100644 --- a/src/index.css +++ b/src/index.css @@ -948,3 +948,56 @@ border: 1px solid #ddd !important; } } + +/* Adaptive difficulty badge styles */ +.bg-green-500 { + background: linear-gradient(135deg, #10b981, #059669); +} + +.bg-yellow-500 { + background: linear-gradient(135deg, #f59e0b, #d97706); +} + +.bg-red-500 { + background: linear-gradient(135deg, #ef4444, #dc2626); +} + +.bg-blue-500 { + background: linear-gradient(135deg, #3b82f6, #1d4ed8); +} + +.text-green-500 { + color: #10b981; +} + +.text-yellow-500 { + color: #f59e0b; +} + +.text-red-500 { + color: #ef4444; +} + +.text-blue-500 { + color: #3b82f6; +} + +.performance-stats { + @apply flex items-center gap-4 text-sm; +} + +.streak-indicator { + @apply px-2 py-1 rounded-full font-semibold; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +@media (max-width: 768px) { + .quiz-info .flex-wrap { + @apply flex-col items-start gap-2; + } + + .performance-stats { + @apply flex-col items-start gap-1; + } +} \ No newline at end of file