Spaces:
Sleeping
Sleeping
| import { GoogleGenAI, Type } from "@google/genai"; | |
| import { AnalysisResult, CsvFilters, Quiz, QuizConfig, UserAnswer } from "../types.ts"; | |
| const quizQuestionSchema = { | |
| type: Type.OBJECT, | |
| properties: { | |
| questionText: { type: Type.STRING, description: "The full text of the question, including any numbered statements. Preserve formatting and line breaks." }, | |
| options: { type: Type.ARRAY, items: { type: Type.STRING }, description: "An array of 4-5 possible answers." }, | |
| correctAnswerIndex: { type: Type.ARRAY, items: { type: Type.INTEGER }, description: "An array of 0-based indices of the correct answer(s). Contains one index for MCQ, multiple for MSQ." }, | |
| questionType: { type: Type.STRING, description: "Should be 'MCQ' for single choice or 'MSQ' for multiple choice." }, | |
| explanation: { type: Type.STRING, description: "A brief explanation for the correct answer." }, | |
| }, | |
| required: ["questionText", "options", "correctAnswerIndex", "questionType", "explanation"] | |
| }; | |
| const quizSchema = { | |
| type: Type.OBJECT, | |
| properties: { | |
| questions: { | |
| type: Type.ARRAY, | |
| items: quizQuestionSchema | |
| } | |
| } | |
| }; | |
| const analysisSchema = { | |
| type: Type.OBJECT, | |
| properties: { | |
| topicAnalysis: { | |
| type: Type.ARRAY, | |
| items: { | |
| type: Type.OBJECT, | |
| properties: { | |
| topic: { type: Type.STRING }, | |
| correct: { type: Type.INTEGER }, | |
| total: { type: Type.INTEGER } | |
| }, | |
| required: ["topic", "correct", "total"] | |
| } | |
| }, | |
| overallFeedback: { type: Type.STRING, description: "A detailed analysis in Markdown format." } | |
| }, | |
| required: ["topicAnalysis", "overallFeedback"] | |
| }; | |
| export const generateQuizFromPdf = async (apiKey: string, base64Pdf: string, config: QuizConfig): Promise<Quiz> => { | |
| const ai = new GoogleGenAI({ apiKey }); | |
| const prompt = ` | |
| Analyze the content of this PDF document and generate a quiz with ${config.numQuestions} questions. | |
| Create a mix of multiple-choice questions (MCQs) and multiple-select questions (MSQs). | |
| The questions should be based on the key information, concepts, and patterns found within the document. | |
| For questions with multiple statements, format the questionText with proper line breaks to ensure readability. | |
| For both MCQs and MSQs, 'correctAnswerIndex' must be an array of numbers. | |
| Return the quiz in the specified JSON format. | |
| `; | |
| const pdfPart = { | |
| inlineData: { | |
| data: base64Pdf, | |
| mimeType: 'application/pdf', | |
| }, | |
| }; | |
| const response = await ai.models.generateContent({ | |
| model: 'gemini-2.5-flash', | |
| contents: { parts: [pdfPart, { text: prompt }] }, | |
| config: { | |
| responseMimeType: "application/json", | |
| responseSchema: quizSchema, | |
| }, | |
| }); | |
| const jsonText = response.text.trim(); | |
| const quizData = JSON.parse(jsonText) as Quiz; | |
| return quizData; | |
| }; | |
| export const generateQuizFromText = async (apiKey: string, text: string, config: QuizConfig): Promise<Quiz> => { | |
| const ai = new GoogleGenAI({ apiKey }); | |
| const instructions = ` | |
| Analyze the provided text content and generate a quiz with ${config.numQuestions} questions based on it. | |
| **Instructions:** | |
| - Create a mix of multiple-choice questions (MCQs) and multiple-select questions (MSQs). | |
| - The questions should be based on the key information and concepts found within the provided text. | |
| - If the text is short, infer the general topic and generate relevant questions about that topic. | |
| - For questions with multiple statements, format the questionText with proper line breaks to ensure readability. | |
| - For both MCQs and MSQs, 'correctAnswerIndex' must be an array of numbers. | |
| - Return the quiz in the specified JSON format. | |
| `; | |
| const fullPrompt = `Here is the source text content:\n\n${text}\n\n${instructions}`; | |
| const response = await ai.models.generateContent({ | |
| model: 'gemini-2.5-flash', | |
| contents: fullPrompt, | |
| config: { | |
| responseMimeType: "application/json", | |
| responseSchema: quizSchema | |
| }, | |
| }); | |
| const jsonText = response.text.trim(); | |
| const quizData = JSON.parse(jsonText) as Quiz; | |
| return quizData; | |
| }; | |
| export const generateQuizFromCsv = async (apiKey: string, csvData: Record<string, string>[], filters: CsvFilters, config: QuizConfig): Promise<Quiz> => { | |
| const ai = new GoogleGenAI({ apiKey }); | |
| const prompt = ` | |
| You are an intelligent quiz curator. I have provided a list of potential quiz questions in JSON format and a set of user-defined criteria. | |
| Your task is to select exactly ${config.numQuestions} questions from the provided list that best match the criteria. | |
| **Criteria:** | |
| ${JSON.stringify(filters, null, 2)} | |
| **Available Questions:** | |
| ${JSON.stringify(csvData.slice(0, 200))} | |
| **Instructions:** | |
| 1. Analyze the criteria and the list of available questions. | |
| 2. Select the ${config.numQuestions} best-matching questions. | |
| 3. Format the selected questions into the required JSON output format. | |
| 4. When formatting the output, map the question data from the CSV to the schema fields. For example, map 'Question_Text' to 'questionText'. | |
| 5. The 'Correct_Option' field in the source data contains a letter ('A', 'B', 'C', 'D') for MCQs or comma-separated letters ('A,C') for MSQs. Convert this to a 0-based array for 'correctAnswerIndex' (A=0, B=1, C=2, D=3). | |
| 6. If an explanation is not available in the source data, generate a brief, accurate one. | |
| 7. Return ONLY the JSON object, with no other text or markdown. | |
| `; | |
| const response = await ai.models.generateContent({ | |
| model: 'gemini-2.5-flash', | |
| contents: prompt, | |
| config: { | |
| responseMimeType: "application/json", | |
| responseSchema: quizSchema, | |
| }, | |
| }); | |
| const jsonText = response.text.trim(); | |
| const quizData = JSON.parse(jsonText) as Quiz; | |
| return quizData; | |
| }; | |
| export const generateQuizFromTopics = async (apiKey: string, topics: string[], config: QuizConfig): Promise<Quiz> => { | |
| const ai = new GoogleGenAI({ apiKey }); | |
| const prompt = ` | |
| You are a quiz generation assistant. Your task is to generate a quiz with ${config.numQuestions} questions covering the following topics: ${topics.join(", ")}. | |
| Create a mix of multiple-choice questions (MCQs) and multiple-select questions (MSQs). | |
| Use your knowledge and web search capabilities to create relevant and challenging questions. | |
| For questions with multiple statements, format the questionText with proper line breaks to ensure readability. | |
| For both MCQs and MSQs, 'correctAnswerIndex' must be an array of numbers. | |
| Return the quiz in the specified JSON format. | |
| `; | |
| const response = await ai.models.generateContent({ | |
| model: 'gemini-2.5-flash', | |
| contents: prompt, | |
| config: { | |
| responseMimeType: "application/json", | |
| responseSchema: quizSchema | |
| }, | |
| }); | |
| let jsonText = response.text.trim(); | |
| const quizData = JSON.parse(jsonText) as Quiz; | |
| return quizData; | |
| }; | |
| export const analyzeQuizResults = async (apiKey: string, quiz: Quiz, userAnswers: UserAnswer[], config: QuizConfig): Promise<AnalysisResult> => { | |
| // 1. Calculate Statistics Locally to ensure strict adherence to user scoring rules | |
| let calculatedScore = 0; | |
| let correctCount = 0; | |
| let incorrectCount = 0; | |
| let unattemptedCount = 0; | |
| const detailedReview = quiz.questions.map((q, i) => { | |
| const userAnswerIndices = userAnswers[i].selectedOption; | |
| const correctIndices = q.correctAnswerIndex; | |
| // Standardize arrays for comparison | |
| const sortedUser = userAnswerIndices ? [...userAnswerIndices].sort((a, b) => a - b) : []; | |
| const sortedCorrect = [...correctIndices].sort((a, b) => a - b); | |
| const isAttempted = userAnswerIndices !== null && userAnswerIndices.length > 0; | |
| let isCorrect = false; | |
| if (isAttempted) { | |
| // Strict equality for correctness (all correct options selected, no extras) | |
| isCorrect = sortedUser.length === sortedCorrect.length && | |
| sortedUser.every((val, index) => val === sortedCorrect[index]); | |
| } | |
| if (!isAttempted) { | |
| unattemptedCount++; | |
| } else if (isCorrect) { | |
| correctCount++; | |
| calculatedScore += config.correctMarks; | |
| } else { | |
| incorrectCount++; | |
| calculatedScore -= config.incorrectMarks; | |
| } | |
| // Ensure answer strings are ready for review | |
| const userAnswerText = userAnswerIndices | |
| ? userAnswerIndices.map(idx => q.options[idx]) | |
| : []; | |
| const correctAnswerText = correctIndices.map(idx => q.options[idx]); | |
| return { | |
| questionText: q.questionText, | |
| userAnswer: userAnswerText, | |
| correctAnswer: correctAnswerText, | |
| isCorrect: isCorrect, | |
| explanation: q.explanation | |
| }; | |
| }); | |
| // Clamp score if needed, or keep raw. Here we keep raw, but max cannot be negative. | |
| const maxScore = quiz.questions.length * config.correctMarks; | |
| // 2. Generate Qualitative Analysis with AI | |
| const ai = new GoogleGenAI({ apiKey }); | |
| const prompt = ` | |
| You are an expert quiz analyst. A user has completed a quiz. | |
| I have already calculated the scores based on strict user-defined rules. | |
| Your task is to provide a "topicAnalysis" and "overallFeedback" based on these results. | |
| **User Config:** | |
| - Marks per correct answer: ${config.correctMarks} | |
| - Deduction per incorrect answer: ${config.incorrectMarks} | |
| **Calculated Performance Stats:** | |
| - Final Score: ${calculatedScore} / ${maxScore} | |
| - Correct Answers: ${correctCount} | |
| - Incorrect Answers: ${incorrectCount} | |
| - Unattempted: ${unattemptedCount} | |
| **Detailed Question Review:** | |
| ${JSON.stringify(detailedReview)} | |
| **Response Requirements:** | |
| 1. **topicAnalysis:** Group questions into 3-5 broad topics. Calculate correct/total based on the 'Detailed Question Review' provided. | |
| 2. **overallFeedback:** Write a helpful summary in Markdown. | |
| - Acknowledge the score: ${calculatedScore}/${maxScore}. | |
| - Discuss strengths and weaknesses based on topics. | |
| - Provide specific advice. | |
| Return the response in the specified JSON format. | |
| `; | |
| const response = await ai.models.generateContent({ | |
| model: 'gemini-2.5-flash', | |
| contents: prompt, | |
| config: { | |
| responseMimeType: "application/json", | |
| responseSchema: analysisSchema, | |
| thinkingConfig: { thinkingBudget: 0 } | |
| } | |
| }); | |
| const jsonText = response.text.trim(); | |
| const aiOutput = JSON.parse(jsonText); | |
| // 3. Merge Local Stats with AI Qualitative Data | |
| // We use local stats for the numbers to prevent AI math hallucinations | |
| const result: AnalysisResult = { | |
| score: calculatedScore, | |
| maxScore: maxScore, | |
| correctCount: correctCount, | |
| incorrectCount: incorrectCount, | |
| unattemptedCount: unattemptedCount, | |
| detailedReview: detailedReview, // Use our locally constructed review | |
| topicAnalysis: aiOutput.topicAnalysis, | |
| overallFeedback: aiOutput.overallFeedback | |
| }; | |
| return result; | |
| }; | |