UNPKG

education-module-ai

Version:
559 lines (544 loc) 19.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.QuizComponent = void 0; const react_1 = __importStar(require("react")); const context_1 = require("../context"); const subjects = [ { value: 'mathematics', label: 'Mathematics' }, { value: 'science', label: 'Science' }, { value: 'history', label: 'History' }, { value: 'language_arts', label: 'Language Arts' }, { value: 'computer_science', label: 'Computer Science' }, { value: 'physics', label: 'Physics' }, { value: 'chemistry', label: 'Chemistry' }, { value: 'biology', label: 'Biology' } ]; const levels = [ { value: 'elementary', label: 'Elementary' }, { value: 'middle_school', label: 'Middle School' }, { value: 'high_school', label: 'High School' }, { value: 'undergraduate', label: 'Undergraduate' }, { value: 'graduate', label: 'Graduate' }, { value: 'professional', label: 'Professional' } ]; // CSS styles as React style objects const styles = { quizComponent: { width: '100%', }, panel: { background: 'white', borderRadius: '8px', boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)', padding: '2rem', width: '100%', }, title: { margin: 0, lineHeight: 1.15, fontSize: '2rem', textAlign: 'center', }, description: { lineHeight: 1.5, fontSize: '1.2rem', textAlign: 'center', marginBottom: '2rem', }, formContainer: { width: '100%', maxWidth: '600px', margin: '0 auto', backgroundColor: '#f9f9f9', padding: '2rem', borderRadius: '10px', boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', }, formGroup: { marginBottom: '1.5rem', }, formLabel: { display: 'block', marginBottom: '0.5rem', fontWeight: 'bold', }, formInput: { width: '100%', padding: '0.8rem', fontSize: '1rem', border: '1px solid #ddd', borderRadius: '5px', }, submitButton: { width: '100%', padding: '1rem', backgroundColor: '#0070f3', color: 'white', border: 'none', borderRadius: '5px', fontSize: '1.1rem', fontWeight: 'bold', cursor: 'pointer', transition: 'background-color 0.2s', }, submitButtonHover: { backgroundColor: '#0051a2', }, submitButtonDisabled: { backgroundColor: '#ccc', cursor: 'not-allowed', }, errorMessage: { padding: '1rem', backgroundColor: '#ffebee', borderLeft: '4px solid #d32f2f', color: '#d32f2f', margin: '1rem 0', borderRadius: '4px', }, quizContainer: { width: '100%', maxWidth: '800px', margin: '0 auto', }, quizHeader: { marginBottom: '2rem', textAlign: 'center', }, quizDescription: { color: '#666', marginBottom: '1.5rem', }, quizProgress: { marginTop: '1rem', textAlign: 'center', fontSize: '0.9rem', color: '#666', }, progressBar: { height: '8px', backgroundColor: '#eaeaea', borderRadius: '4px', marginTop: '0.5rem', overflow: 'hidden', }, progressFill: (width) => ({ height: '100%', backgroundColor: '#0070f3', transition: 'width 0.3s ease', width, }), questionCard: { backgroundColor: 'white', padding: '2rem', borderRadius: '10px', boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)', }, questionText: { fontSize: '1.3rem', fontWeight: 500, marginBottom: '1.5rem', }, optionsList: { display: 'flex', flexDirection: 'column', gap: '1rem', marginBottom: '2rem', }, option: (isSelected) => ({ display: 'flex', alignItems: 'center', padding: '1rem', border: `2px solid ${isSelected ? '#0070f3' : '#eaeaea'}`, borderRadius: '8px', cursor: 'pointer', transition: 'all 0.2s', backgroundColor: isSelected ? '#f0f7ff' : 'transparent', }), optionMarker: (isSelected) => ({ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '30px', height: '30px', borderRadius: '50%', backgroundColor: isSelected ? '#0070f3' : '#eaeaea', color: isSelected ? 'white' : '#333', fontWeight: 'bold', marginRight: '1rem', }), optionText: { flex: 1, }, navigationButtons: { display: 'flex', justifyContent: 'space-between', }, navButton: { padding: '0.8rem 1.5rem', backgroundColor: '#0070f3', color: 'white', border: 'none', borderRadius: '5px', fontSize: '1rem', cursor: 'pointer', transition: 'background-color 0.2s', }, navButtonDisabled: { backgroundColor: '#ccc', cursor: 'not-allowed', }, resultsContainer: { width: '100%', maxWidth: '800px', margin: '0 auto', backgroundColor: 'white', padding: '2rem', borderRadius: '10px', boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)', }, resultsTitle: { textAlign: 'center', marginBottom: '2rem', }, scoreDisplay: { display: 'flex', justifyContent: 'center', marginBottom: '3rem', }, scoreCircle: { position: 'relative', width: '150px', height: '150px', borderRadius: '50%', backgroundColor: '#f0f7ff', border: '8px solid #0070f3', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', }, scorePercentage: { fontSize: '2.5rem', fontWeight: 'bold', color: '#0070f3', }, scoreFraction: { fontSize: '1rem', color: '#666', }, questionReview: { marginTop: '2rem', }, questionReviewTitle: { marginBottom: '1.5rem', textAlign: 'center', }, reviewQuestion: (isCorrect) => ({ marginBottom: '2rem', padding: '1.5rem', borderRadius: '8px', backgroundColor: '#f9f9f9', borderLeft: `4px solid ${isCorrect ? '#4caf50' : '#f44336'}`, }), questionNumber: { fontWeight: 'bold', marginRight: '0.5rem', }, answerResults: { margin: '1rem 0', }, correctAnswer: { color: '#4caf50', fontWeight: 'bold', }, wrongAnswer: { color: '#f44336', fontWeight: 'bold', }, explanation: { padding: '1rem', backgroundColor: '#f0f7ff', borderRadius: '5px', marginTop: '1rem', }, actionButtons: { display: 'flex', justifyContent: 'center', gap: '1.5rem', marginTop: '2rem', }, actionButton: { padding: '0.8rem 1.5rem', backgroundColor: '#0070f3', color: 'white', border: 'none', borderRadius: '5px', fontSize: '1rem', cursor: 'pointer', transition: 'background-color 0.2s', }, }; const QuizComponent = ({ title = "Quiz Generator", description = "Generate a quiz on any topic to test your knowledge", onComplete, className = "" }) => { const { apiBaseUrl } = (0, context_1.useConfig)(); const [formState, setFormState] = (0, react_1.useState)({ subject: 'computer_science', level: 'undergraduate', topic: '', questionCount: 5 }); const [isLoading, setIsLoading] = (0, react_1.useState)(false); const [error, setError] = (0, react_1.useState)(null); const [quiz, setQuiz] = (0, react_1.useState)(null); const [activeQuestion, setActiveQuestion] = (0, react_1.useState)(0); const [selectedAnswers, setSelectedAnswers] = (0, react_1.useState)({}); const [showResults, setShowResults] = (0, react_1.useState)(false); const handleInputChange = (e) => { const { name, value } = e.target; setFormState(prev => ({ ...prev, [name]: value })); }; const handleSubmit = async (e) => { e.preventDefault(); if (!formState.topic.trim()) return; setIsLoading(true); setError(null); setQuiz(null); setSelectedAnswers({}); setShowResults(false); try { console.log("Sending quiz request to backend:", { url: `${apiBaseUrl}/api/educational-contents/quiz`, body: { subject: formState.subject, level: formState.level, topic: formState.topic, questionCount: parseInt(formState.questionCount.toString()) } }); const response = await fetch(`${apiBaseUrl}/api/educational-contents/quiz`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ subject: formState.subject, level: formState.level, topic: formState.topic, questionCount: parseInt(formState.questionCount.toString()) }), }); console.log("Quiz response status:", response.status); if (!response.ok) { const errorData = await response.json().catch(() => null); console.error('Error response:', errorData); throw new Error(errorData && errorData.error ? errorData.error : `Server responded with ${response.status}: ${response.statusText}`); } const data = await response.json(); setQuiz(data.data); setActiveQuestion(0); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to generate quiz'); console.error('Error generating quiz:', err); } finally { setIsLoading(false); } }; const handleAnswerSelect = (questionIndex, answer) => { setSelectedAnswers(prev => ({ ...prev, [questionIndex]: answer })); }; const handleNextQuestion = () => { if (quiz && activeQuestion < quiz.questions.length - 1) { setActiveQuestion(activeQuestion + 1); } else { setShowResults(true); } }; const handlePrevQuestion = () => { if (activeQuestion > 0) { setActiveQuestion(activeQuestion - 1); } }; const calculateScore = () => { if (!quiz) return { correctCount: 0, totalCount: 0, percentage: 0 }; let correctCount = 0; quiz.questions.forEach((question, index) => { if (selectedAnswers[index] === question.correctAnswer) { correctCount++; } }); const score = { correctCount, totalCount: quiz.questions.length, percentage: Math.round((correctCount / quiz.questions.length) * 100) }; if (showResults && onComplete) { onComplete(score); } return score; }; const resetQuiz = () => { setSelectedAnswers({}); setActiveQuestion(0); setShowResults(false); }; return (<div style={{ ...styles.quizComponent, ...(className ? { className } : {}) }}> <div style={styles.panel}> <h1 style={styles.title}>{title}</h1> <p style={styles.description}> {description} </p> {!quiz ? (<div style={styles.formContainer}> <form onSubmit={handleSubmit}> <div style={styles.formGroup}> <label style={styles.formLabel} htmlFor="subject">Subject</label> <select id="subject" name="subject" value={formState.subject} onChange={handleInputChange} style={styles.formInput}> {subjects.map(subject => (<option key={subject.value} value={subject.value}> {subject.label} </option>))} </select> </div> <div style={styles.formGroup}> <label style={styles.formLabel} htmlFor="level">Level</label> <select id="level" name="level" value={formState.level} onChange={handleInputChange} style={styles.formInput}> {levels.map(level => (<option key={level.value} value={level.value}> {level.label} </option>))} </select> </div> <div style={styles.formGroup}> <label style={styles.formLabel} htmlFor="topic">Topic</label> <input type="text" id="topic" name="topic" value={formState.topic} onChange={handleInputChange} placeholder="e.g., Data Structures, World War II, Photosynthesis" style={styles.formInput} required/> </div> <div style={styles.formGroup}> <label style={styles.formLabel} htmlFor="questionCount">Number of Questions</label> <input type="number" id="questionCount" name="questionCount" value={formState.questionCount} onChange={handleInputChange} min="1" max="10" style={styles.formInput}/> </div> <button type="submit" style={{ ...styles.submitButton, ...(isLoading || !formState.topic.trim() ? styles.submitButtonDisabled : {}) }} disabled={isLoading || !formState.topic.trim()}> {isLoading ? 'Generating Quiz...' : 'Generate Quiz'} </button> </form> </div>) : showResults ? (<div style={styles.resultsContainer}> <h2 style={styles.resultsTitle}>Quiz Results</h2> <div style={styles.scoreDisplay}> <div style={styles.scoreCircle}> <span style={styles.scorePercentage}>{calculateScore().percentage}%</span> <span style={styles.scoreFraction}> {calculateScore().correctCount}/{calculateScore().totalCount} </span> </div> </div> <div style={styles.questionReview}> <h3 style={styles.questionReviewTitle}>Review Questions</h3> {quiz.questions.map((question, index) => (<div key={index} style={styles.reviewQuestion(selectedAnswers[index] === question.correctAnswer)}> <p style={styles.questionText}> <span style={styles.questionNumber}>{index + 1}.</span> {question.question} </p> <div style={styles.answerResults}> <p> Your answer: <span style={selectedAnswers[index] === question.correctAnswer ? styles.correctAnswer : styles.wrongAnswer}> {selectedAnswers[index] || 'No answer'} </span> </p> {selectedAnswers[index] !== question.correctAnswer && (<p>Correct answer: <span style={styles.correctAnswer}>{question.correctAnswer}</span></p>)} </div> <div style={styles.explanation}> <p>{question.explanation}</p> </div> </div>))} </div> <div style={styles.actionButtons}> <button onClick={resetQuiz} style={styles.actionButton}>Retake Quiz</button> <button onClick={() => setQuiz(null)} style={styles.actionButton}>Create New Quiz</button> </div> </div>) : (<div style={styles.quizContainer}> <div style={styles.quizHeader}> <h2>{quiz.title}</h2> <p style={styles.quizDescription}>{quiz.description}</p> <div style={styles.quizProgress}> Question {activeQuestion + 1} of {quiz.questions.length} <div style={styles.progressBar}> <div style={styles.progressFill(`${((activeQuestion + 1) / quiz.questions.length) * 100}%`)}></div> </div> </div> </div> <div style={styles.questionCard}> <p style={styles.questionText}>{quiz.questions[activeQuestion].question}</p> <div style={styles.optionsList}> {quiz.questions[activeQuestion].options.map((option, index) => (<div key={index} style={styles.option(selectedAnswers[activeQuestion] === option)} onClick={() => handleAnswerSelect(activeQuestion, option)}> <span style={styles.optionMarker(selectedAnswers[activeQuestion] === option)}> {String.fromCharCode(65 + index)} </span> <span style={styles.optionText}>{option}</span> </div>))} </div> <div style={styles.navigationButtons}> <button onClick={handlePrevQuestion} disabled={activeQuestion === 0} style={{ ...styles.navButton, ...(activeQuestion === 0 ? styles.navButtonDisabled : {}) }}> Previous </button> <button onClick={handleNextQuestion} style={{ ...styles.navButton, ...(!selectedAnswers[activeQuestion] ? styles.navButtonDisabled : {}) }} disabled={!selectedAnswers[activeQuestion]}> {activeQuestion === quiz.questions.length - 1 ? 'Finish Quiz' : 'Next Question'} </button> </div> </div> </div>)} {error && (<div style={styles.errorMessage}> <p>{error}</p> <p>Make sure your backend server is running at http://localhost:3009</p> </div>)} </div> </div>); }; exports.QuizComponent = QuizComponent;