diff --git a/server.js b/server.js index 9c1946d..052a317 100644 --- a/server.js +++ b/server.js @@ -1281,63 +1281,123 @@ app.post('/api/generate-quiz', requireAuth, async (req, res) => { - // Call Flowise API - const response = await axios.post(`${FLOWISE_API_URL}/${FLOWISE_CHATFLOW_ID}`, { - question: prompt, - history: [] - }); - - - + // Call Flowise API with retry logic for better AI responses let quizData; - try { - const responseText = response.data.text || response.data.answer || response.data; + let retryCount = 0; + const maxRetries = 2; + + while (retryCount <= maxRetries) { + try { + const response = await axios.post(`${FLOWISE_API_URL}/${FLOWISE_CHATFLOW_ID}`, { + question: retryCount > 0 ? + `${prompt}\n\nIMPORTANT: Please provide ONLY a valid JSON array of questions. Do not include any explanatory text, greetings, or additional commentary. Start your response directly with [ and end with ].` : + prompt, + history: [] + }); - - // Try to extract JSON from the response - let jsonString = null; - - // First, try to find JSON wrapped in code blocks - const codeBlockMatch = responseText.match(/```(?:json)?\s*(\[[\s\S]*?\])\s*```/); - if (codeBlockMatch) { - jsonString = codeBlockMatch[1]; + const responseText = response.data.text || response.data.answer || response.data; - } else { - // Try to find JSON array by counting brackets - const startIndex = responseText.indexOf('['); - if (startIndex !== -1) { - let bracketCount = 0; - let endIndex = startIndex; + // Check if the AI response looks like it's not following instructions + if (responseText.toLowerCase().includes('do you have a question') || + responseText.toLowerCase().includes('would you like me to help') || + responseText.toLowerCase().includes('something else related') || + responseText.toLowerCase().includes('how can i help') || + (!responseText.includes('[') && !responseText.includes('{'))) { - for (let i = startIndex; i < responseText.length; i++) { - if (responseText[i] === '[') bracketCount++; - if (responseText[i] === ']') bracketCount--; - if (bracketCount === 0) { - endIndex = i; - break; - } - } - - if (bracketCount === 0) { - jsonString = responseText.substring(startIndex, endIndex + 1); - + if (retryCount < maxRetries) { + retryCount++; + console.log(`AI gave improper response, retrying... (attempt ${retryCount + 1})`); + continue; + } else { + throw new Error('The AI is not responding properly to quiz generation requests. Please try again with a more specific topic or try again later.'); } } - } - - if (jsonString) { + + // Try to extract JSON from the response + let jsonString = null; + + // First, try to find JSON wrapped in code blocks + const codeBlockMatch = responseText.match(/```(?:json)?\s*(\[[\s\S]*?\])\s*```/); + if (codeBlockMatch) { + jsonString = codeBlockMatch[1]; + } else { + // Try to find JSON array by counting brackets + const startIndex = responseText.indexOf('['); + if (startIndex !== -1) { + let bracketCount = 0; + let endIndex = startIndex; + + for (let i = startIndex; i < responseText.length; i++) { + if (responseText[i] === '[') bracketCount++; + if (responseText[i] === ']') bracketCount--; + if (bracketCount === 0) { + endIndex = i; + break; + } + } + + if (bracketCount === 0) { + jsonString = responseText.substring(startIndex, endIndex + 1); + } + } + } + + if (!jsonString) { + if (retryCount < maxRetries) { + retryCount++; + console.log(`Could not find JSON in response, retrying... (attempt ${retryCount + 1})`); + continue; + } else { + throw new Error('Could not find valid JSON in the AI response after multiple attempts. Please try generating the quiz again.'); + } + } + quizData = JSON.parse(jsonString); - } else { - quizData = generateFallbackQuiz(topic, questionCount, quizType); + + // Validate the parsed data + if (!Array.isArray(quizData) || quizData.length === 0) { + if (retryCount < maxRetries) { + retryCount++; + console.log(`Invalid quiz format received, retrying... (attempt ${retryCount + 1})`); + continue; + } else { + throw new Error('The AI generated an invalid quiz format after multiple attempts. Please try again.'); + } + } + + // Validate each question has required fields + for (let i = 0; i < quizData.length; i++) { + const q = quizData[i]; + if (!q.question || (!q.correct && !q.answer)) { + if (retryCount < maxRetries) { + retryCount++; + console.log(`Question ${i + 1} missing required fields, retrying... (attempt ${retryCount + 1})`); + continue; + } else { + throw new Error(`Question ${i + 1} is missing required fields after multiple attempts. Please try generating the quiz again.`); + } + } + } + + // If we get here, the quiz is valid + break; + + } catch (parseError) { + if (retryCount < maxRetries) { + retryCount++; + console.log(`Parse error occurred, retrying... (attempt ${retryCount + 1}):`, parseError.message); + continue; + } else { + console.error('Quiz parsing error after retries:', parseError); + console.error('AI Response was:', response?.data?.text || response?.data?.answer || response?.data || 'No response'); + + // Return error instead of fallback + return res.json({ + success: false, + error: parseError.message || 'Failed to parse quiz questions from AI response after multiple attempts. Please try again with a different topic or rephrase your request.' + }); + } } - } catch (parseError) { - console.error('Quiz parsing error:', parseError); - quizData = generateFallbackQuiz(topic, questionCount, quizType); - } - - // Ensure quizData is always defined and is an array - if (!quizData || !Array.isArray(quizData) || quizData.length === 0) { - quizData = generateFallbackQuiz(topic, questionCount, quizType); } @@ -1354,154 +1414,14 @@ app.post('/api/generate-quiz', requireAuth, async (req, res) => { console.error('Quiz generation error:', error); console.error('Error stack:', error.stack); - // Return fallback quiz on error - const fallbackQuiz = generateFallbackQuiz(req.body.topic || 'General Knowledge', req.body.questionCount || 5, req.body.quizType || 'multiple-choice'); + // Return error instead of fallback quiz res.json({ - success: true, - quiz: fallbackQuiz, - topic: req.body.topic || 'General Knowledge', - difficulty: req.body.difficulty || 'beginner', - questionCount: req.body.questionCount || 5, - quizType: req.body.quizType || 'multiple-choice' + success: false, + error: 'Failed to generate quiz. Please check your connection and try again, or try a different topic.' }); } }); -function generateFallbackQuiz(topic, questionCount, quizType) { - const questions = []; - - // Generate actual questions based on topic - const topicQuestions = { - 'javascript': [ - { - question: 'What is the correct way to declare a variable in JavaScript?', - options: ['A) var myVar = 5;', 'B) variable myVar = 5;', 'C) declare myVar = 5;', 'D) int myVar = 5;'], - correct: 'A', - explanation: 'The var keyword is used to declare variables in JavaScript.' - }, - { - question: 'Which method is used to add an element to the end of an array?', - options: ['A) push()', 'B) add()', 'C) append()', 'D) insert()'], - correct: 'A', - explanation: 'The push() method adds one or more elements to the end of an array.' - }, - { - question: 'What does === operator do in JavaScript?', - options: ['A) Assigns a value', 'B) Compares values only', 'C) Compares values and types', 'D) Declares a constant'], - correct: 'C', - explanation: 'The === operator compares both value and type without type conversion.' - }, - { - question: 'How do you write a comment in JavaScript?', - options: ['A) ', 'B) // comment', 'C) # comment', 'D) /* comment */'], - correct: 'B', - explanation: 'Single-line comments in JavaScript start with //.' - }, - { - question: 'What is the result of typeof null in JavaScript?', - options: ['A) "null"', 'B) "undefined"', 'C) "object"', 'D) "boolean"'], - correct: 'C', - explanation: 'typeof null returns "object" due to a legacy bug in JavaScript.' - } - ], - 'python': [ - { - question: 'Which keyword is used to create a function in Python?', - options: ['A) function', 'B) def', 'C) create', 'D) func'], - correct: 'B', - explanation: 'The def keyword is used to define functions in Python.' - }, - { - question: 'What is the correct way to create a list in Python?', - options: ['A) list = (1, 2, 3)', 'B) list = {1, 2, 3}', 'C) list = [1, 2, 3]', 'D) list = <1, 2, 3>'], - correct: 'C', - explanation: 'Square brackets [] are used to create lists in Python.' - }, - { - question: 'How do you start a comment in Python?', - options: ['A) //', 'B) #', 'C) /*', 'D) --'], - correct: 'B', - explanation: 'Comments in Python start with the # symbol.' - }, - { - question: 'What does len() function do in Python?', - options: ['A) Returns the length of an object', 'B) Converts to lowercase', 'C) Rounds a number', 'D) Prints output'], - correct: 'A', - explanation: 'The len() function returns the number of items in an object.' - }, - { - question: 'Which of the following is a mutable data type in Python?', - options: ['A) tuple', 'B) string', 'C) list', 'D) integer'], - correct: 'C', - explanation: 'Lists are mutable, meaning they can be changed after creation.' - } - ], - 'math': [ - { - question: 'What is the value of π (pi) approximately?', - options: ['A) 3.14159', 'B) 2.71828', 'C) 1.61803', 'D) 4.66920'], - correct: 'A', - explanation: 'π (pi) is approximately 3.14159, the ratio of circumference to diameter.' - }, - { - question: 'What is the derivative of x²?', - options: ['A) x', 'B) 2x', 'C) x³', 'D) 2x²'], - correct: 'B', - explanation: 'Using the power rule, the derivative of x² is 2x.' - }, - { - question: 'What is the Pythagorean theorem?', - options: ['A) a + b = c', 'B) a² + b² = c²', 'C) a × b = c', 'D) a² - b² = c²'], - correct: 'B', - explanation: 'The Pythagorean theorem states that a² + b² = c² for right triangles.' - }, - { - question: 'What is the factorial of 5?', - options: ['A) 25', 'B) 120', 'C) 60', 'D) 100'], - correct: 'B', - explanation: '5! = 5 × 4 × 3 × 2 × 1 = 120' - }, - { - question: 'What is the square root of 64?', - options: ['A) 6', 'B) 7', 'C) 8', 'D) 9'], - correct: 'C', - explanation: 'The square root of 64 is 8 because 8² = 64.' - } - ] - }; - - // Get appropriate questions for the topic - const availableQuestions = topicQuestions[topic.toLowerCase()] || topicQuestions['math']; - - for (let i = 0; i < questionCount; i++) { - const questionIndex = i % availableQuestions.length; - const baseQuestion = availableQuestions[questionIndex]; - - if (quizType === 'multiple-choice') { - questions.push({ - question: baseQuestion.question, - options: baseQuestion.options, - correct: baseQuestion.correct, - explanation: baseQuestion.explanation - }); - } else if (quizType === 'true-false') { - questions.push({ - question: baseQuestion.question.replace(/Which|What|How/, 'Is it true that'), - correct: Math.random() > 0.5 ? 'True' : 'False', - explanation: baseQuestion.explanation - }); - } else { - questions.push({ - question: baseQuestion.question, - answer: baseQuestion.correct, - keywords: [baseQuestion.correct.toLowerCase(), topic.toLowerCase()] - }); - } - } - - return questions; -} - app.post('/api/submit-quiz', requireAuth, async (req, res) => { try { const { answers, quiz, topic, difficulty, quizType } = req.body; diff --git a/views/quiz.ejs b/views/quiz.ejs index 4ee6392..e6a1ec9 100644 --- a/views/quiz.ejs +++ b/views/quiz.ejs @@ -143,7 +143,7 @@
${escapeHtml(question.question)}
+${escapeHtml(resultForThisQuestion.explanation)}
+