pvyas96's picture
Upload 19 files
89f2e0a verified
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;
};