From 9e897ca14a38cc196b3248077519adf548fc57c6 Mon Sep 17 00:00:00 2001 From: Emmy123567 Date: Fri, 1 Aug 2025 02:17:01 +0000 Subject: [PATCH] Connect Groq Cloud AI Integration and Remove Mock Data --- src/App.tsx | 228 +++++++++++------- src/components/ApiKeyValidator.tsx | 102 ++++++++ src/components/ChatCoach.tsx | 259 -------------------- src/components/ErrorBoundary.tsx | 86 +++++++ src/components/Flashcards.tsx | 292 ---------------------- src/components/Header.tsx | 116 --------- src/components/Quiz.tsx | 317 ------------------------ src/components/StudyMaterialUpload.tsx | 222 ----------------- src/components/StudyPlan.tsx | 229 ------------------ src/components/StudyStrategies.tsx | 109 ++++++++- src/components/Summary.tsx | 96 -------- src/main.tsx | 14 +- src/utils/api.ts | 320 +++++++++++++++++-------- src/utils/security.ts | 152 ++++++++++++ src/utils/storage.ts | 117 +++++++++ 15 files changed, 936 insertions(+), 1723 deletions(-) create mode 100644 src/components/ApiKeyValidator.tsx delete mode 100644 src/components/ChatCoach.tsx create mode 100644 src/components/ErrorBoundary.tsx delete mode 100644 src/components/Flashcards.tsx delete mode 100644 src/components/Header.tsx delete mode 100644 src/components/Quiz.tsx delete mode 100644 src/components/StudyMaterialUpload.tsx delete mode 100644 src/components/StudyPlan.tsx delete mode 100644 src/components/Summary.tsx create mode 100644 src/utils/security.ts create mode 100644 src/utils/storage.ts diff --git a/src/App.tsx b/src/App.tsx index 2a468c3..7f414f3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,45 +6,29 @@ import ExamRegistration from './components/ExamRegistration'; import StudyStrategies from './components/StudyStrategies'; import ExamResultSubmission from './components/ExamResultSubmission'; import StarBackground from './components/StarBackground'; -import { ExamRegistration as ExamRegistrationType, ExamQuestion, ExamResult } from './types'; - -// Mock exam questions for demo -const MOCK_QUESTIONS: ExamQuestion[] = [ - { - id: '1', - examType: 'usmle-step1', - state: 'CA', - question: 'A 45-year-old patient presents with chest pain. What is the most likely diagnosis?', - options: ['Myocardial infarction', 'Gastroesophageal reflux', 'Pulmonary embolism', 'Anxiety disorder', 'Costochondritis'], - correctAnswer: 0, - explanation: 'Given the age and presentation, myocardial infarction should be the primary consideration.', - difficulty: 'medium', - timeAllowed: 90, - category: 'cardiology', - verified: true, - createdAt: new Date(), - }, - { - id: '2', - examType: 'bar-exam', - state: 'NY', - question: 'In contract law, what constitutes a valid offer?', - options: ['Intent to be bound', 'Definite terms', 'Communication to offeree', 'All of the above', 'None of the above'], - correctAnswer: 3, - explanation: 'A valid offer requires intent, definite terms, and communication to the offeree.', - difficulty: 'easy', - timeAllowed: 120, - category: 'contracts', - verified: true, - createdAt: new Date(), - }, -]; +import { ExamRegistration as ExamRegistrationType, ExamResult, UserAnswer } from './types'; +import { validateApiKey } from './utils/api'; function App() { const [currentView, setCurrentView] = useState<'landing' | 'registration' | 'study' | 'results'>('landing'); const [currentRegistration, setCurrentRegistration] = useState(null); const [darkMode, setDarkMode] = useState(true); const [examExpired, setExamExpired] = useState(false); + const [apiKeyValid, setApiKeyValid] = useState(null); + + // Validate API key on app start + useEffect(() => { + const checkApiKey = async () => { + try { + const isValid = await validateApiKey(); + setApiKeyValid(isValid); + } catch { + setApiKeyValid(false); + } + }; + + checkApiKey(); + }, []); useEffect(() => { // Check if exam date has passed @@ -63,42 +47,72 @@ function App() { setExamExpired(false); setCurrentView('study'); - // If failed, could load questions from successful candidates here - if (result.actualExamResult === 'fail') { - console.log('Loading questions from successful candidates...'); + // Update registration with result + if (currentRegistration) { + const updatedRegistration = { + ...currentRegistration, + examResult: result.actualExamResult, + updatedAt: new Date(), + }; + setCurrentRegistration(updatedRegistration); + localStorage.setItem('exam-registration', JSON.stringify(updatedRegistration)); } }; - const handleStudySessionComplete = (answers: any[], score: number) => { - console.log('Study session completed:', { answers, score }); + const handleStudySessionComplete = (answers: UserAnswer[], score: number) => { + console.log('Study session completed:', { + answers: answers.length, + score, + examType: currentRegistration?.examType.name, + state: currentRegistration?.state.name + }); + + // Save session results to localStorage for analytics + const sessionResult = { + timestamp: new Date(), + examType: currentRegistration?.examType.id, + state: currentRegistration?.state.code, + score, + totalQuestions: answers.length, + correctAnswers: answers.filter(a => a.isCorrect).length, + }; + + const savedResults = JSON.parse(localStorage.getItem('study-sessions') || '[]'); + savedResults.push(sessionResult); + localStorage.setItem('study-sessions', JSON.stringify(savedResults)); }; - // Check if user needs to report exam results + // Load registration from localStorage useEffect(() => { - if (examExpired && currentRegistration && !currentRegistration.examResult) { - setCurrentView('results'); - } - }, []); - - useEffect(() => { - // Load registration from localStorage const saved = localStorage.getItem('exam-registration'); if (saved) { - const registration = JSON.parse(saved); - setCurrentRegistration(registration); - if (registration.isActive) { - setCurrentView('study'); + try { + const registration = JSON.parse(saved); + setCurrentRegistration(registration); + if (registration.isActive && registration.paymentStatus === 'completed') { + setCurrentView('study'); + } + } catch (error) { + console.error('Failed to load registration from localStorage:', error); + localStorage.removeItem('exam-registration'); } } }, []); + // Save registration to localStorage useEffect(() => { - // Save registration to localStorage if (currentRegistration) { localStorage.setItem('exam-registration', JSON.stringify(currentRegistration)); } }, [currentRegistration]); + // Check if user needs to report exam results + useEffect(() => { + if (examExpired && currentRegistration && !currentRegistration.examResult) { + setCurrentView('results'); + } + }, [examExpired, currentRegistration]); + return ( void; currentRegistration: ExamRegistrationType | null; examExpired: boolean; + apiKeyValid: boolean | null; onRegistrationComplete: (registration: ExamRegistrationType) => void; onResultSubmitted: (result: ExamResult) => void; - onStudySessionComplete: (answers: any[], score: number) => void; + onStudySessionComplete: (answers: UserAnswer[], score: number) => void; } const AppContent: React.FC = ({ @@ -135,6 +151,7 @@ const AppContent: React.FC = ({ setDarkMode, currentRegistration, examExpired, + apiKeyValid, onRegistrationComplete, onResultSubmitted, onStudySessionComplete, @@ -142,6 +159,23 @@ const AppContent: React.FC = ({ const { user, isLoading, error, signIn } = useUser(); const renderContent = () => { + // Show API key error if invalid + if (apiKeyValid === false) { + return ( +
+
+

Configuration Error

+

+ Groq API key is not configured or invalid. Please check your environment variables. +

+

+ Add your Groq API key to the .env file as VITE_GROQ_API_KEY +

+
+
+ ); + } + switch (currentView) { case 'landing': return setCurrentView('registration')} />; @@ -151,22 +185,45 @@ const AppContent: React.FC = ({ if (examExpired) { return (
-

Study Features Deactivated

-

- Your exam date has passed. Please report your results to reactivate your study features. -

- +
+

Study Features Deactivated

+

+ Your exam date has passed. Please report your results to reactivate your study features. +

+ +
+
+ ); + } + + if (!currentRegistration) { + return ( +
+
+

No Active Registration

+

+ Please complete your exam registration to access study features. +

+ +
); } + return ( ); @@ -182,12 +239,12 @@ const AppContent: React.FC = ({ } }; - // If authentication is loading, show a loading state - if (isLoading) { + // Loading state + if (isLoading || apiKeyValid === null) { return (
-
+

Loading...

@@ -195,36 +252,39 @@ const AppContent: React.FC = ({ ); } - // If there's an error, show it + // Authentication error if (error) { return (
-
-

Error: {error.message}

- +
+
+

Authentication Error

+

{error.message}

+ +
); } - // If no user is authenticated, show a login prompt + // Not authenticated if (!user) { return (
-
+

Welcome to TutorBot

@@ -246,7 +306,7 @@ const AppContent: React.FC = ({ ); } - // If authenticated, render the full app + // Authenticated - render full app return (
diff --git a/src/components/ApiKeyValidator.tsx b/src/components/ApiKeyValidator.tsx new file mode 100644 index 0000000..8794bb0 --- /dev/null +++ b/src/components/ApiKeyValidator.tsx @@ -0,0 +1,102 @@ +import React, { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import { Key, CheckCircle, XCircle, Loader2 } from 'lucide-react'; +import { validateApiKey } from '../utils/api'; + +interface ApiKeyValidatorProps { + onValidationComplete: (isValid: boolean) => void; +} + +const ApiKeyValidator: React.FC = ({ onValidationComplete }) => { + const [isValidating, setIsValidating] = useState(true); + const [isValid, setIsValid] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + validateKey(); + }, []); + + const validateKey = async () => { + setIsValidating(true); + setError(null); + + try { + const valid = await validateApiKey(); + setIsValid(valid); + onValidationComplete(valid); + + if (!valid) { + setError('Groq API key is not configured or invalid. Please check your environment variables.'); + } + } catch (err) { + setIsValid(false); + setError('Failed to validate API key. Please check your internet connection.'); + onValidationComplete(false); + } finally { + setIsValidating(false); + } + }; + + if (isValidating) { + return ( + +
+ +

Validating API configuration...

+
+
+ ); + } + + if (isValid === false) { + return ( + +
+ +

Configuration Error

+

{error}

+ +
+

To fix this:

+
    +
  1. 1. Get your Groq API key from groq.com
  2. +
  3. 2. Add it to your .env file as VITE_GROQ_API_KEY
  4. +
  5. 3. Restart the development server
  6. +
+
+ + +
+
+ ); + } + + return ( + +
+ +

API configuration validated successfully!

+
+
+ ); +}; + +export default ApiKeyValidator; \ No newline at end of file diff --git a/src/components/ChatCoach.tsx b/src/components/ChatCoach.tsx deleted file mode 100644 index cf9ea7c..0000000 --- a/src/components/ChatCoach.tsx +++ /dev/null @@ -1,259 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; -import { Send, Bot, User, Loader2 } from 'lucide-react'; -import { StudySession, ChatMessage } from '../types'; -import { generateChatResponse } from '../utils/api'; - -interface ChatCoachProps { - session: StudySession; - onUpdate: (session: StudySession) => void; -} - -const ChatCoach: React.FC = ({ session, onUpdate }) => { - const [messages, setMessages] = useState(session.chatHistory || []); - const [input, setInput] = useState(''); - const [loading, setLoading] = useState(false); - const messagesEndRef = useRef(null); - - useEffect(() => { - scrollToBottom(); - }, [messages]); - - useEffect(() => { - // Initialize chat with daily questions if no messages exist - if (messages.length === 0) { - initializeDailyQuestions(); - } - }, []); - - const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }; - - const initializeDailyQuestions = async () => { - const welcomeMessage: ChatMessage = { - id: Date.now().toString(), - type: 'bot', - content: `Hello! I'm your AI Study Coach. I'm here to help you review your study material on "${session.title}". Let me start with some questions to test your understanding.`, - timestamp: new Date(), - questionType: 'encouragement', - }; - - const updatedMessages = [welcomeMessage]; - setMessages(updatedMessages); - - // Generate daily questions - try { - const dailyQuestions = await generateChatResponse( - session.content, - 'Generate 3 review questions about this study material to help the student review. Make them varied in difficulty.', - [] - ); - - const questionMessage: ChatMessage = { - id: (Date.now() + 1).toString(), - type: 'bot', - content: dailyQuestions, - timestamp: new Date(), - questionType: 'review', - }; - - const finalMessages = [...updatedMessages, questionMessage]; - setMessages(finalMessages); - - const updatedSession = { - ...session, - chatHistory: finalMessages, - lastAccessed: new Date(), - }; - onUpdate(updatedSession); - } catch (error) { - console.error('Failed to generate daily questions:', error); - } - }; - - const handleSend = async () => { - if (!input.trim() || loading) return; - - const userMessage: ChatMessage = { - id: Date.now().toString(), - type: 'user', - content: input, - timestamp: new Date(), - }; - - const updatedMessages = [...messages, userMessage]; - setMessages(updatedMessages); - setInput(''); - setLoading(true); - - try { - const response = await generateChatResponse( - session.content, - input, - messages.slice(-10) // Send last 10 messages for context - ); - - const botMessage: ChatMessage = { - id: (Date.now() + 1).toString(), - type: 'bot', - content: response, - timestamp: new Date(), - questionType: 'review', - }; - - const finalMessages = [...updatedMessages, botMessage]; - setMessages(finalMessages); - - const updatedSession = { - ...session, - chatHistory: finalMessages, - lastAccessed: new Date(), - }; - onUpdate(updatedSession); - } catch (error) { - console.error('Failed to get chat response:', error); - const errorMessage: ChatMessage = { - id: (Date.now() + 1).toString(), - type: 'bot', - content: 'Sorry, I encountered an error. Please try again.', - timestamp: new Date(), - }; - setMessages([...updatedMessages, errorMessage]); - } finally { - setLoading(false); - } - }; - - const handleKeyPress = (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - handleSend(); - } - }; - - return ( - -
-

- AI Study Coach -

-

- Get personalized help and daily questions from your AI tutor -

-
- -
- {/* Chat Messages */} -
- - {messages.map((message) => ( - -
-
- {message.type === 'bot' ? ( - - ) : ( - - )} - - {message.type === 'bot' ? 'AI Coach' : 'You'} - -
-

{message.content}

-

- {new Date(message.timestamp).toLocaleTimeString()} -

-
-
- ))} -
- - {loading && ( - -
-
- - AI Coach -
-
- - Thinking... -
-
-
- )} - -
-
- - {/* Chat Input */} -
-
- setInput(e.target.value)} - onKeyPress={handleKeyPress} - placeholder="Ask a question about your study material..." - className="flex-1 bg-gray-700 text-white rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-500" - disabled={loading} - /> - -
-
-
- - {/* Study Tips */} -
-
-

Daily Questions

-

- I'll ask you 3-5 questions daily to help reinforce your learning -

-
-
-

Weakness Tracking

-

- I'll identify areas where you need more practice -

-
-
-

Encouragement

-

- I'll provide tips and motivation to keep you going -

-
-
- - ); -}; - -export default ChatCoach; \ No newline at end of file diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000..041aa65 --- /dev/null +++ b/src/components/ErrorBoundary.tsx @@ -0,0 +1,86 @@ +import React, { Component, ErrorInfo, ReactNode } from 'react'; +import { AlertCircle, RefreshCw } from 'lucide-react'; +import { logSecureError } from '../utils/security'; + +interface Props { + children: ReactNode; +} + +interface State { + hasError: boolean; + error?: Error; + errorInfo?: ErrorInfo; +} + +class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + this.setState({ errorInfo }); + logSecureError(error, 'React Error Boundary'); + } + + handleReset = () => { + this.setState({ hasError: false, error: undefined, errorInfo: undefined }); + }; + + render() { + if (this.state.hasError) { + return ( +
+
+ + +

+ Something went wrong +

+ +

+ We encountered an unexpected error. This has been logged and we'll work to fix it. +

+ +
+ + + +
+ + {process.env.NODE_ENV === 'development' && this.state.error && ( +
+ + Error Details (Development) + +
+                  {this.state.error.toString()}
+                  {this.state.errorInfo?.componentStack}
+                
+
+ )} +
+
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; \ No newline at end of file diff --git a/src/components/Flashcards.tsx b/src/components/Flashcards.tsx deleted file mode 100644 index ab034d4..0000000 --- a/src/components/Flashcards.tsx +++ /dev/null @@ -1,292 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; -import { ChevronLeft, ChevronRight, RotateCcw, Check, X, Loader2 } from 'lucide-react'; -import { StudySession, Flashcard } from '../types'; -import { generateFlashcards } from '../utils/api'; - -interface FlashcardsProps { - session: StudySession; - onUpdate: (session: StudySession) => void; -} - -const Flashcards: React.FC = ({ session, onUpdate }) => { - const [currentIndex, setCurrentIndex] = useState(0); - const [isFlipped, setIsFlipped] = useState(false); - const [loading, setLoading] = useState(false); - const [flashcards, setFlashcards] = useState(session.flashcards || []); - - useEffect(() => { - if (!session.flashcards || session.flashcards.length === 0) { - generateCards(); - } - }, []); - - const generateCards = async () => { - setLoading(true); - try { - const cards = await generateFlashcards(session.content); - setFlashcards(cards); - - const updatedSession = { - ...session, - flashcards: cards, - lastAccessed: new Date(), - }; - onUpdate(updatedSession); - } catch (error) { - console.error('Failed to generate flashcards:', error); - } finally { - setLoading(false); - } - }; - - const handleAnswer = (mastered: boolean) => { - const updatedCards = [...flashcards]; - updatedCards[currentIndex] = { - ...updatedCards[currentIndex], - mastered, - reviewCount: updatedCards[currentIndex].reviewCount + 1, - lastReviewed: new Date(), - }; - - setFlashcards(updatedCards); - - const updatedSession = { - ...session, - flashcards: updatedCards, - progress: { - ...session.progress, - flashcardsCompleted: updatedCards.filter(c => c.mastered).length, - }, - lastAccessed: new Date(), - }; - onUpdate(updatedSession); - - // Move to next card - if (currentIndex < flashcards.length - 1) { - setCurrentIndex(currentIndex + 1); - } else { - setCurrentIndex(0); - } - setIsFlipped(false); - }; - - const nextCard = () => { - setCurrentIndex((prev) => (prev + 1) % flashcards.length); - setIsFlipped(false); - }; - - const prevCard = () => { - setCurrentIndex((prev) => (prev - 1 + flashcards.length) % flashcards.length); - setIsFlipped(false); - }; - - const resetProgress = () => { - const resetCards = flashcards.map(card => ({ - ...card, - mastered: false, - reviewCount: 0, - lastReviewed: undefined, - })); - - setFlashcards(resetCards); - - const updatedSession = { - ...session, - flashcards: resetCards, - progress: { - ...session.progress, - flashcardsCompleted: 0, - }, - lastAccessed: new Date(), - }; - onUpdate(updatedSession); - }; - - if (loading) { - return ( - -
- -

Generating flashcards...

-
-
- ); - } - - if (flashcards.length === 0) { - return ( - -
-

No flashcards available

- -
-
- ); - } - - const currentCard = flashcards[currentIndex]; - const masteredCount = flashcards.filter(c => c.mastered).length; - - return ( - -
-

- Flashcards -

-

- Review your study material with AI-generated flashcards -

-
- - {/* Progress Bar */} -
-
- - Progress: {masteredCount} / {flashcards.length} mastered - - -
-
-
-
-
- - {/* Card Counter */} -
- - {currentIndex + 1} of {flashcards.length} - -
- - {/* Flashcard */} -
- - -
setIsFlipped(!isFlipped)} - > - {/* Front of card */} -
-
-

Question

-

- {currentCard.question} -

-
-
- - {/* Back of card */} -
-
-

Answer

-

- {currentCard.answer} -

-
-
-
-
-
-
- - {/* Navigation Controls */} -
- - -
- - - -
- - -
- - {/* Card Stats */} -
-
-

{currentCard.reviewCount}

-

Reviews

-
- -
-

{masteredCount}

-

Mastered

-
- -
-

{flashcards.length - masteredCount}

-

Remaining

-
- -
-

{currentCard.difficulty}

-

Difficulty

-
-
- - ); -}; - -export default Flashcards; \ No newline at end of file diff --git a/src/components/Header.tsx b/src/components/Header.tsx deleted file mode 100644 index 820a21f..0000000 --- a/src/components/Header.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import React from 'react'; -import { motion } from 'framer-motion'; -import { - Upload, - BookOpen, - Brain, - HelpCircle, - Calendar, - MessageCircle, - Sun, - Moon, - Sparkles -} from 'lucide-react'; -import { CivicAuthProvider } from '@civic/auth/react'; -import CivicLoginButton from './CivicLoginButton'; - -interface HeaderProps { - activeTab: string; - setActiveTab: (tab: string) => void; - darkMode: boolean; - setDarkMode: (dark: boolean) => void; - hasSession: boolean; -} - -const Header: React.FC = ({ - activeTab, - setActiveTab, - darkMode, - setDarkMode, - hasSession -}) => { - const tabs = [ - { id: 'upload', label: 'Upload', icon: Upload }, - { id: 'summary', label: 'Summary', icon: BookOpen, disabled: !hasSession }, - { id: 'flashcards', label: 'Flashcards', icon: Brain, disabled: !hasSession }, - { id: 'quiz', label: 'Quiz', icon: HelpCircle, disabled: !hasSession }, - { id: 'plan', label: 'Study Plan', icon: Calendar, disabled: !hasSession }, - { id: 'chat', label: 'AI Coach', icon: MessageCircle, disabled: !hasSession }, - ]; - - return ( - -
-
-
- -
- -
-
-
-

- TutorBot -

-

AI Study Coach

-
-
- - - - - -
-
-
-
- ); -}; - -export default Header; \ No newline at end of file diff --git a/src/components/Quiz.tsx b/src/components/Quiz.tsx deleted file mode 100644 index 9aa57b2..0000000 --- a/src/components/Quiz.tsx +++ /dev/null @@ -1,317 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; -import { CheckCircle, XCircle, Trophy, RotateCcw, Loader2 } from 'lucide-react'; -import { StudySession, QuizQuestion } from '../types'; -import { generateQuiz } from '../utils/api'; - -interface QuizProps { - session: StudySession; - onUpdate: (session: StudySession) => void; -} - -const Quiz: React.FC = ({ session, onUpdate }) => { - const [currentQuestion, setCurrentQuestion] = useState(0); - const [selectedAnswer, setSelectedAnswer] = useState(null); - const [showResult, setShowResult] = useState(false); - const [quizComplete, setQuizComplete] = useState(false); - const [loading, setLoading] = useState(false); - const [quiz, setQuiz] = useState(session.quiz || []); - - useEffect(() => { - if (!session.quiz || session.quiz.length === 0) { - generateQuizQuestions(); - } - }, []); - - const generateQuizQuestions = async () => { - setLoading(true); - try { - const questions = await generateQuiz(session.content); - setQuiz(questions); - - const updatedSession = { - ...session, - quiz: questions, - lastAccessed: new Date(), - }; - onUpdate(updatedSession); - } catch (error) { - console.error('Failed to generate quiz:', error); - } finally { - setLoading(false); - } - }; - - const handleAnswer = (answerIndex: number) => { - setSelectedAnswer(answerIndex); - - const updatedQuiz = [...quiz]; - updatedQuiz[currentQuestion] = { - ...updatedQuiz[currentQuestion], - answered: true, - userAnswer: answerIndex, - isCorrect: answerIndex === updatedQuiz[currentQuestion].correctAnswer, - }; - - setQuiz(updatedQuiz); - setShowResult(true); - - // Update session - const score = updatedQuiz.filter(q => q.isCorrect).length; - const updatedSession = { - ...session, - quiz: updatedQuiz, - progress: { - ...session.progress, - quizScore: Math.round((score / updatedQuiz.length) * 100), - }, - lastAccessed: new Date(), - }; - onUpdate(updatedSession); - }; - - const nextQuestion = () => { - if (currentQuestion < quiz.length - 1) { - setCurrentQuestion(currentQuestion + 1); - setSelectedAnswer(null); - setShowResult(false); - } else { - setQuizComplete(true); - } - }; - - const resetQuiz = () => { - const resetQuiz = quiz.map(q => ({ - ...q, - answered: false, - userAnswer: undefined, - isCorrect: undefined, - })); - - setQuiz(resetQuiz); - setCurrentQuestion(0); - setSelectedAnswer(null); - setShowResult(false); - setQuizComplete(false); - - const updatedSession = { - ...session, - quiz: resetQuiz, - progress: { - ...session.progress, - quizScore: 0, - }, - lastAccessed: new Date(), - }; - onUpdate(updatedSession); - }; - - if (loading) { - return ( - -
- -

Generating quiz questions...

-
-
- ); - } - - if (quiz.length === 0) { - return ( - -
-

No quiz available

- -
-
- ); - } - - const currentQ = quiz[currentQuestion]; - const score = quiz.filter(q => q.isCorrect).length; - const totalAnswered = quiz.filter(q => q.answered).length; - - if (quizComplete) { - return ( - -
- -

- Quiz Complete! -

-

- Your Score: {score} / {quiz.length} ({Math.round((score / quiz.length) * 100)}%) -

- -
-
-

{score}

-

Correct

-
-
-

{quiz.length - score}

-

Incorrect

-
-
-

{Math.round((score / quiz.length) * 100)}%

-

Score

-
-
- - -
-
- ); - } - - return ( - -
-

- Quiz Time -

-

- Test your knowledge with AI-generated questions -

-
- - {/* Progress */} -
-
- - Question {currentQuestion + 1} of {quiz.length} - - - Score: {score} / {totalAnswered} - -
-
-
-
-
- - {/* Question */} - - -
- - {currentQ.difficulty} - - {currentQ.type} -
- -

- {currentQ.question} -

- -
- {currentQ.options.map((option, index) => ( - - ))} -
- - {showResult && ( - -
-
- {selectedAnswer === currentQ.correctAnswer ? ( - - ) : ( - - )} - - {selectedAnswer === currentQ.correctAnswer ? 'Correct!' : 'Incorrect'} - -
- -
-
- )} -
-
- - ); -}; - -export default Quiz; \ No newline at end of file diff --git a/src/components/StudyMaterialUpload.tsx b/src/components/StudyMaterialUpload.tsx deleted file mode 100644 index 8392ce3..0000000 --- a/src/components/StudyMaterialUpload.tsx +++ /dev/null @@ -1,222 +0,0 @@ -import React, { useState } from 'react'; -import { motion } from 'framer-motion'; -import { useDropzone } from 'react-dropzone'; -import { Upload, FileText, Type, Loader2 } from 'lucide-react'; -import { StudySession } from '../types'; -import { generateAISummary } from '../utils/api'; - -interface StudyMaterialUploadProps { - onSessionCreate: (session: StudySession) => void; -} - -const StudyMaterialUpload: React.FC = ({ onSessionCreate }) => { - const [uploadMethod, setUploadMethod] = useState<'file' | 'text' | 'topic'>('file'); - const [textContent, setTextContent] = useState(''); - const [topicTitle, setTopicTitle] = useState(''); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - - const { getRootProps, getInputProps, isDragActive } = useDropzone({ - accept: { - 'text/plain': ['.txt'], - 'application/pdf': ['.pdf'], - }, - maxFiles: 1, - onDrop: async (acceptedFiles) => { - const file = acceptedFiles[0]; - if (file) { - setLoading(true); - setError(null); - - try { - const text = await file.text(); - await createSession(file.name, text); - } catch (err) { - setError('Failed to read file. Please try again.'); - } finally { - setLoading(false); - } - } - }, - }); - - const createSession = async (title: string, content: string) => { - try { - const summary = await generateAISummary(content); - - const session: StudySession = { - id: Date.now().toString(), - title, - content, - summary, - createdAt: new Date(), - lastAccessed: new Date(), - progress: { - flashcardsCompleted: 0, - quizScore: 0, - studyDaysCompleted: 0, - }, - }; - - onSessionCreate(session); - } catch (err) { - setError('Failed to process content. Please try again.'); - } - }; - - const handleTextSubmit = async () => { - if (!textContent.trim()) return; - - setLoading(true); - setError(null); - - try { - await createSession('Text Input', textContent); - } finally { - setLoading(false); - } - }; - - const handleTopicSubmit = async () => { - if (!topicTitle.trim()) return; - - setLoading(true); - setError(null); - - try { - // For topic input, we'll create a basic prompt for the AI - const prompt = `Please provide a comprehensive study guide for the topic: "${topicTitle}". Include key concepts, important details, and explanations that would be helpful for a student learning this subject.`; - await createSession(topicTitle, prompt); - } finally { - setLoading(false); - } - }; - - return ( - -
-

- Start Your Study Session -

-

- Upload your study material or enter a topic to get personalized AI assistance -

-
- - {/* Upload Method Tabs */} -
-
- {[ - { id: 'file', label: 'Upload File', icon: Upload }, - { id: 'text', label: 'Paste Text', icon: FileText }, - { id: 'topic', label: 'Enter Topic', icon: Type }, - ].map((method) => { - const Icon = method.icon; - return ( - - ); - })} -
-
- - {error && ( -
- {error} -
- )} - - {/* Upload Content */} -
- {uploadMethod === 'file' && ( -
- - -

- {isDragActive ? 'Drop your file here' : 'Drag & drop your study material'} -

-

- Supports PDF and text files • Max 1 file -

-
- )} - - {uploadMethod === 'text' && ( -
-