education-module-ai
Version:
AI Education Module using Agent Framework
559 lines (544 loc) • 19.6 kB
JSX
;
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;