@thanh01.pmt/interactive-quiz-kit 1.0.27 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8067,20 +8067,12 @@ var CodingQuestionZodSchema = BaseQuestionZodSchema.extend({
8067
8067
  })).min(1)
8068
8068
  });
8069
8069
 
8070
- // src/ai/flows/question-gen/generate-fitb-question-types.ts
8071
- BaseQuestionGenerationClientInputSchema.extend({
8072
- numberOfBlanks: zod.z.number().int().min(1).max(5).optional().default(1),
8073
- isCaseSensitive: zod.z.boolean().optional().default(false)
8074
- });
8075
- var AIFillInTheBlanksOutputFieldsSchema = zod.z.object({
8076
- prompt: zod.z.string().describe("The instructional text for the user, e.g., 'Fill in the blanks to complete the sentence.'"),
8077
- // Yêu cầu AI trả về cấu trúc segments trực tiếp
8078
- segments: zod.z.array(zod.z.object({
8079
- type: zod.z.enum(["text", "blank"]),
8080
- content: zod.z.string().optional().describe("The text content for a 'text' segment."),
8081
- acceptedAnswers: zod.z.array(zod.z.string().min(1)).min(1).optional().describe("An array of correct answers for a 'blank' segment.")
8082
- })).min(1).describe("An array of text and blank segments representing the question."),
8083
- explanation: zod.z.string().optional(),
8070
+ // src/ai/flows/question-gen/generate-true-false-question-types.ts
8071
+ BaseQuestionGenerationClientInputSchema.extend({});
8072
+ var AITrueFalseOutputFieldsSchema = zod.z.object({
8073
+ prompt: zod.z.string().describe("The statement that the user will evaluate as true or false."),
8074
+ correctAnswer: zod.z.boolean(),
8075
+ explanation: zod.z.string().optional().describe("An explanation of why the statement is true or false, especially important if false."),
8084
8076
  points: zod.z.number().optional().default(10),
8085
8077
  difficulty: zod.z.enum(["easy", "medium", "hard"]).optional(),
8086
8078
  topic: zod.z.string().optional(),
@@ -8088,57 +8080,55 @@ var AIFillInTheBlanksOutputFieldsSchema = zod.z.object({
8088
8080
  // Thêm để xác thực
8089
8081
  });
8090
8082
 
8091
- // src/ai/flows/question-gen/generate-fitb-question.ts
8083
+ // src/ai/flows/question-gen/generate-true-false-question.ts
8092
8084
  var MAX_RETRY_ATTEMPTS = 3;
8093
8085
  var RETRY_DELAY_MS = 3e3;
8094
8086
  function buildEnhancedPrompt(clientInput, attemptNumber) {
8095
- const { quizContext, language: language3, difficulty, numberOfBlanks, imageUrl } = clientInput;
8087
+ const { quizContext, language: language3, difficulty, imageUrl } = clientInput;
8096
8088
  const category = quizContext?.originalCategory || "the specified technical category";
8097
8089
  const attemptInfo = attemptNumber > 1 ? `
8098
8090
  ## DEBUG INFO - This is attempt #${attemptNumber}
8099
- Previous attempts failed. Pay strict attention to the JSON schema, especially the 'segments' array structure. Ensure 'blank' segments have 'acceptedAnswers' and 'text' segments have 'content'.
8091
+ Previous attempts failed. Ensure the JSON is valid and 'correctAnswer' is a boolean.
8100
8092
 
8101
8093
  ` : "";
8102
- const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and blanks must be directly related to the content of this image.` : "";
8094
+ const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The True/False statement must be directly related to the content of this image.` : "";
8095
+ const misconceptionGuidance = quizContext?.targetMisconception ? `**Target Misconception:** The statement you create MUST be FALSE and based on this common mistake: "${quizContext.targetMisconception}"` : "";
8103
8096
  const contextStrings = [
8104
8097
  `**Required Category:** ${category}`,
8105
8098
  quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
8106
8099
  imageContextInstruction,
8107
8100
  quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
8108
- quizContext?.targetMisconception && `**Target Misconception:** Design the blank to test this specific point: "${quizContext.targetMisconception}"`
8101
+ misconceptionGuidance,
8102
+ quizContext?.difficultyReason && `**Pedagogical Reason:** ${quizContext.difficultyReason}`
8109
8103
  ].filter(Boolean).map((s2) => `- ${s2}`).join("\n");
8110
8104
  const exampleJson = JSON.stringify({
8111
- prompt: "Complete the following Swift code snippet.",
8112
- segments: [
8113
- { "type": "text", "content": "To declare a new function in Swift, you use the `" },
8114
- { "type": "blank", "acceptedAnswers": ["func"] },
8115
- { "type": "text", "content": "` keyword." }
8116
- ],
8117
- explanation: "The 'func' keyword is used to declare a function in the Swift programming language.",
8105
+ prompt: "In Swift, you must explicitly unwrap an Optional value before you can use its stored value.",
8106
+ correctAnswer: true,
8107
+ explanation: "Optional values in Swift represent the presence or absence of a value. To access the value when it exists, you must unwrap it using methods like 'if let', 'guard let', or the force unwrap operator '!'.",
8118
8108
  points: 10,
8119
8109
  difficulty: "easy",
8120
- topic: "Swift Function Declaration",
8110
+ topic: "Swift Optionals",
8121
8111
  verifiedCategory: category
8122
8112
  }, null, 2);
8123
8113
  return `${attemptInfo}You are an expert Question Author for advanced technical education, specializing in: ${category}.
8124
- Your mission is to create a high-quality, technically accurate Fill-in-the-Blanks Question.
8114
+ Your mission is to create a high-quality, technically accurate True/False Question.
8125
8115
 
8126
8116
  ## Core Rules (Non-negotiable)
8127
- 1. **Category Purity:** The question MUST be exclusively about **${category}**.
8128
- 2. **Schema Integrity:** The response MUST be ONLY a single, valid JSON object that strictly follows the provided schema.
8129
- 3. **Logical Segments:** For 'blank' segments, you MUST provide 'acceptedAnswers'. For 'text' segments, you MUST provide 'content'. Do not mix them.
8117
+ 1. **Category Purity:** The statement ('prompt') MUST be exclusively about **${category}**.
8118
+ 2. **Clarity:** The statement must be definitively true or false, with no ambiguity.
8119
+ 3. **Misconception Priority:** If a Target Misconception is provided, the statement MUST be FALSE and reflect that misconception. This is a critical rule.
8120
+ 4. **Schema Integrity:** The response MUST be ONLY a single, valid JSON object.
8130
8121
 
8131
8122
  ## CRITICAL CONTEXT FOR THIS QUESTION
8132
8123
  ${contextStrings}
8133
8124
 
8134
8125
  ## Task: Generate the Question
8135
- Based on all the rules and context above, generate a single Fill-in-the-Blanks Question.
8126
+ Based on all the rules and context above, generate a single True/False Question.
8136
8127
 
8137
8128
  ### Input Parameters
8138
8129
  - **Topic for Question:** ${quizContext?.plannedTopic || "General"}
8139
8130
  - **Language for Text:** ${language3}
8140
8131
  - **Difficulty Level:** ${difficulty}
8141
- - **Number of Blanks:** Generate exactly ${numberOfBlanks} segment(s) with type 'blank'.
8142
8132
 
8143
8133
  ### Required JSON Output Format
8144
8134
  Your response must be ONLY the JSON object, matching this exact structure:
@@ -8147,11 +8137,11 @@ ${exampleJson}
8147
8137
 
8148
8138
  Now, generate the JSON for the requested question.`;
8149
8139
  }
8150
- async function generateFillInTheBlanksQuestion(clientInput, apiKey) {
8140
+ async function generateTrueFalseQuestion(clientInput, apiKey) {
8151
8141
  const ai = new genai.GoogleGenAI({ apiKey });
8152
8142
  const model = "gemini-2.5-flash";
8153
8143
  const config2 = {
8154
- temperature: 0.8,
8144
+ temperature: 0.6,
8155
8145
  responseMimeType: "application/json",
8156
8146
  thinkingConfig: {
8157
8147
  thinkingBudget: 4e3
@@ -8180,20 +8170,11 @@ async function generateFillInTheBlanksQuestion(clientInput, apiKey) {
8180
8170
  if (!rawText) throw new Error("AI returned an empty response.");
8181
8171
  const parsedJson = JSON.parse(rawText);
8182
8172
  DebugLogger.logValidation(attempt, "JSON Parsed Successfully", parsedJson);
8183
- const aiGeneratedContent = AIFillInTheBlanksOutputFieldsSchema.parse(parsedJson);
8173
+ const aiGeneratedContent = AITrueFalseOutputFieldsSchema.parse(parsedJson);
8184
8174
  DebugLogger.logValidation(attempt, "Zod Schema Validated", aiGeneratedContent);
8185
- const blankCount = aiGeneratedContent.segments.filter((s2) => s2.type === "blank").length;
8186
- if (blankCount !== clientInput.numberOfBlanks) {
8187
- throw new Error(`AI generated ${blankCount} blanks, but ${clientInput.numberOfBlanks} were required.`);
8175
+ if (clientInput.quizContext?.targetMisconception && aiGeneratedContent.correctAnswer === true) {
8176
+ throw new Error("AI failed to follow the Misconception Priority rule. The answer should have been false.");
8188
8177
  }
8189
- aiGeneratedContent.segments.forEach((segment, index3) => {
8190
- if (segment.type === "blank" && (!segment.acceptedAnswers || segment.acceptedAnswers.length === 0)) {
8191
- throw new Error(`Segment ${index3} is a 'blank' but is missing 'acceptedAnswers'.`);
8192
- }
8193
- if (segment.type === "text" && typeof segment.content !== "string") {
8194
- throw new Error(`Segment ${index3} is 'text' but is missing 'content'.`);
8195
- }
8196
- });
8197
8178
  if (clientInput.quizContext?.originalCategory) {
8198
8179
  const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
8199
8180
  const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
@@ -8201,24 +8182,11 @@ async function generateFillInTheBlanksQuestion(clientInput, apiKey) {
8201
8182
  throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
8202
8183
  }
8203
8184
  }
8204
- const finalSegments = [];
8205
- const finalAnswers = [];
8206
- aiGeneratedContent.segments.forEach((segment) => {
8207
- if (segment.type === "text") {
8208
- finalSegments.push({ type: "text", content: segment.content });
8209
- } else if (segment.type === "blank" && segment.acceptedAnswers) {
8210
- const blankId = generateUniqueId("blank_");
8211
- finalSegments.push({ type: "blank", id: blankId });
8212
- finalAnswers.push({ blankId, acceptedValues: segment.acceptedAnswers });
8213
- }
8214
- });
8215
8185
  const completeQuestion = {
8216
- id: generateUniqueId("fitb_ai_"),
8217
- questionType: "fill_in_the_blanks",
8186
+ id: generateUniqueId("tf_ai_"),
8187
+ questionType: "true_false",
8218
8188
  prompt: aiGeneratedContent.prompt,
8219
- segments: finalSegments,
8220
- answers: finalAnswers,
8221
- isCaseSensitive: clientInput.isCaseSensitive,
8189
+ correctAnswer: aiGeneratedContent.correctAnswer,
8222
8190
  explanation: aiGeneratedContent.explanation,
8223
8191
  points: aiGeneratedContent.points,
8224
8192
  topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
@@ -8230,10 +8198,10 @@ async function generateFillInTheBlanksQuestion(clientInput, apiKey) {
8230
8198
  category: clientInput.quizContext?.originalCategory,
8231
8199
  imageUrl: clientInput.imageUrl
8232
8200
  };
8233
- const validatedQuestion = FillInTheBlanksQuestionZodSchema.parse(completeQuestion);
8201
+ const validatedQuestion = TrueFalseQuestionZodSchema.parse(completeQuestion);
8234
8202
  attemptResults.push({ success: true, duration, promptLength: promptText.length, responseLength: rawText.length, promptHash });
8235
8203
  console.log(`
8236
- \u2705 FITB generation successful on attempt ${attempt} (${duration}ms)`);
8204
+ \u2705 True/False generation successful on attempt ${attempt} (${duration}ms)`);
8237
8205
  if (attempt > 1) DebugLogger.logAttemptSummary(attemptResults);
8238
8206
  return { question: validatedQuestion };
8239
8207
  } catch (error) {
@@ -8249,89 +8217,93 @@ async function generateFillInTheBlanksQuestion(clientInput, apiKey) {
8249
8217
  }
8250
8218
  }
8251
8219
  DebugLogger.logAttemptSummary(attemptResults);
8252
- const errorMessage = `Failed to generate FITB question after ${MAX_RETRY_ATTEMPTS} attempts. Last error: ${lastError?.message}`;
8220
+ const errorMessage = `Failed to generate True/False question after ${MAX_RETRY_ATTEMPTS} attempts. Last error: ${lastError?.message}`;
8253
8221
  console.error("\n\u274C Final Result: FAILED");
8254
8222
  console.error(errorMessage);
8255
8223
  return { error: errorMessage };
8256
8224
  }
8257
8225
  BaseQuestionGenerationClientInputSchema.extend({
8258
- numberOfPairs: zod.z.number().int().min(2).max(8).optional().default(4),
8259
- shuffleOptions: zod.z.boolean().optional().default(true)
8226
+ numberOfOptions: zod.z.number().int().min(2).max(6).optional().default(4)
8260
8227
  });
8261
- var AIMatchingOutputFieldsSchema = zod.z.object({
8262
- prompt: zod.z.string().describe("The instructional text for the user, e.g., 'Match the concept to its definition.'"),
8263
- correctPairs: zod.z.array(zod.z.object({
8264
- promptText: zod.z.string().min(1).describe("The text for the left-hand side item (the prompt)."),
8265
- optionText: zod.z.string().min(1).describe("The text for the right-hand side item (the matching option).")
8266
- })).min(2),
8267
- explanation: zod.z.string().optional(),
8228
+ var AIMCQOutputFieldsSchema = zod.z.object({
8229
+ prompt: zod.z.string().describe("The question statement itself."),
8230
+ options: zod.z.array(
8231
+ zod.z.object({
8232
+ tempId: zod.z.string().describe("A temporary, unique identifier for this option (e.g., 'A', 'B', '1', '2')."),
8233
+ text: zod.z.string().describe("The text content of this answer option.")
8234
+ })
8235
+ ).min(2).max(6),
8236
+ correctTempOptionId: zod.z.string().describe("The temporary ID of the correct option from the generated options array."),
8237
+ explanation: zod.z.string().optional().describe("A brief explanation of why the answer is correct."),
8268
8238
  points: zod.z.number().optional().default(10),
8269
8239
  difficulty: zod.z.enum(["easy", "medium", "hard"]).optional(),
8270
8240
  topic: zod.z.string().optional(),
8271
- verifiedCategory: zod.z.string().optional()
8272
- // Thêm để xác thực
8241
+ verifiedCategory: zod.z.string().optional().describe("The category this question actually addresses.")
8273
8242
  });
8274
8243
 
8275
- // src/ai/flows/question-gen/generate-matching-question.ts
8244
+ // src/ai/flows/question-gen/generate-mcq-question.ts
8276
8245
  var MAX_RETRY_ATTEMPTS2 = 3;
8277
8246
  var RETRY_DELAY_MS2 = 3e3;
8278
8247
  function buildEnhancedPrompt2(clientInput, attemptNumber) {
8279
- const { quizContext, language: language3, difficulty, numberOfPairs, imageUrl } = clientInput;
8248
+ const { quizContext, language: language3, difficulty, numberOfOptions, imageUrl } = clientInput;
8280
8249
  const category = quizContext?.originalCategory || "the specified technical category";
8281
8250
  const attemptInfo = attemptNumber > 1 ? `
8282
8251
  ## DEBUG INFO - This is attempt #${attemptNumber}
8283
- Previous attempts failed. Please ensure the 'correctPairs' array has exactly the required number of items and the JSON is valid.
8252
+ Previous attempts failed...
8284
8253
 
8285
8254
  ` : "";
8286
- const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The matching pairs must be directly related to the content of this image.` : "";
8255
+ const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and options must be directly related to the content of this image.` : "";
8287
8256
  const contextStrings = [
8288
8257
  `**Required Category:** ${category}`,
8289
8258
  quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
8290
8259
  imageContextInstruction,
8291
8260
  quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
8292
- quizContext?.targetMisconception && `**Target Misconception:** Design a pair that specifically tests this confusion: "${quizContext.targetMisconception}"`
8261
+ quizContext?.targetMisconception && `**Target Misconception:** Use this to create plausible incorrect answers: "${quizContext.targetMisconception}"`,
8262
+ quizContext?.difficultyReason && `**Pedagogical Reason:** ${quizContext.difficultyReason}`
8293
8263
  ].filter(Boolean).map((s2) => `- ${s2}`).join("\n");
8294
8264
  const exampleJson = JSON.stringify({
8295
- prompt: "Match each Swift collection type to its primary characteristic.",
8296
- correctPairs: [
8297
- { "promptText": "Array", "optionText": "An ordered, random-access collection." },
8298
- { "promptText": "Set", "optionText": "An unordered collection of unique elements." },
8299
- { "promptText": "Dictionary", "optionText": "An unordered collection of key-value associations." }
8265
+ prompt: `In ${category}, what is the primary purpose of the 'guard' statement?`,
8266
+ options: [
8267
+ { tempId: "A", text: "To execute a block of code repeatedly." },
8268
+ { tempId: "B", text: "To define a new custom data type." },
8269
+ { tempId: "C", text: "To exit a scope early if a condition is not met." },
8270
+ { tempId: "D", text: "To handle errors thrown by a function." }
8300
8271
  ],
8301
- explanation: "These are the fundamental characteristics of Swift's main collection types.",
8272
+ correctTempOptionId: "C",
8273
+ explanation: `The 'guard' statement in ${category} provides an early exit from a scope (like a function) if a condition is false, enhancing code readability by handling required conditions upfront.`,
8302
8274
  points: 10,
8303
8275
  difficulty: "easy",
8304
- topic: "Swift Collection Types",
8276
+ topic: `Control Flow in ${category}`,
8305
8277
  verifiedCategory: category
8306
8278
  }, null, 2);
8307
- return `${attemptInfo}You are an expert Question Author for advanced technical education, specializing in: ${category}.
8308
- Your mission is to create a high-quality, technically accurate Matching Question.
8279
+ return `${attemptInfo}You are an expert Question Author for advanced technical education, specializing in the programming language: ${category}.
8280
+ Your sole mission is to create a high-quality, technically accurate Multiple Choice Question. You must adhere to the following rules at all times.
8309
8281
 
8310
8282
  ## Core Rules (Non-negotiable)
8311
- 1. **Category Purity:** The question MUST be exclusively about **${category}**.
8312
- 2. **Logical Pairs:** The items to be matched must have a clear, one-to-one relationship.
8313
- 3. **Schema Integrity:** The response MUST be ONLY a single, valid JSON object that strictly follows the provided schema.
8283
+ 1. **Category Purity:** The question, options, and explanation MUST be exclusively about **${category}**. Do NOT mention or use syntax from other languages.
8284
+ 2. **Context Adherence:** The question's content must directly align with all provided context.
8285
+ 3. **Format Integrity:** You MUST return ONLY a single, valid JSON object that strictly follows the provided schema. Do not include any extra text, comments, or markdown formatting.
8314
8286
 
8315
8287
  ## CRITICAL CONTEXT FOR THIS QUESTION
8316
8288
  ${contextStrings}
8317
8289
 
8318
8290
  ## Task: Generate the Question
8319
- Based on all the rules and context above, generate a single Matching Question.
8291
+ Based on all the rules and context above, generate a single Multiple Choice Question.
8320
8292
 
8321
8293
  ### Input Parameters
8322
8294
  - **Topic for Question:** ${quizContext?.plannedTopic || "General"}
8323
8295
  - **Language for Text:** ${language3}
8324
8296
  - **Difficulty Level:** ${difficulty}
8325
- - **Number of Pairs:** Generate exactly ${numberOfPairs} correct pairs in the 'correctPairs' array.
8297
+ - **Number of Options:** ${numberOfOptions}
8326
8298
 
8327
8299
  ### Required JSON Output Format
8328
- Your response must be ONLY the JSON object, matching this exact structure:
8300
+ Your response must be ONLY the JSON object, matching this exact structure and field names.
8329
8301
 
8330
8302
  ${exampleJson}
8331
8303
 
8332
8304
  Now, generate the JSON for the requested question.`;
8333
8305
  }
8334
- async function generateMatchingQuestion(clientInput, apiKey) {
8306
+ async function generateMCQQuestion(clientInput, apiKey) {
8335
8307
  const ai = new genai.GoogleGenAI({ apiKey });
8336
8308
  const model = "gemini-2.5-flash";
8337
8309
  const config2 = {
@@ -8356,19 +8328,22 @@ async function generateMatchingQuestion(clientInput, apiKey) {
8356
8328
  parts.unshift(imagePart);
8357
8329
  }
8358
8330
  const contents = [{ role: "user", parts }];
8359
- const aiResult = await ai.models.generateContent({ model, config: config2, contents });
8331
+ const aiResult = await ai.models.generateContent({
8332
+ model,
8333
+ config: config2,
8334
+ contents
8335
+ });
8360
8336
  const response = aiResult;
8361
8337
  const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
8362
8338
  const duration = Date.now() - startTime;
8363
8339
  DebugLogger.logResponse(attempt, rawText);
8364
- if (!rawText) throw new Error("AI returned an empty response.");
8340
+ if (!rawText) {
8341
+ throw new Error("AI returned an empty response.");
8342
+ }
8365
8343
  const parsedJson = JSON.parse(rawText);
8366
8344
  DebugLogger.logValidation(attempt, "JSON Parsed Successfully", parsedJson);
8367
- const aiGeneratedContent = AIMatchingOutputFieldsSchema.parse(parsedJson);
8345
+ const aiGeneratedContent = AIMCQOutputFieldsSchema.parse(parsedJson);
8368
8346
  DebugLogger.logValidation(attempt, "Zod Schema Validated", aiGeneratedContent);
8369
- if (aiGeneratedContent.correctPairs.length !== clientInput.numberOfPairs) {
8370
- throw new Error(`AI generated ${aiGeneratedContent.correctPairs.length} pairs, but ${clientInput.numberOfPairs} were required.`);
8371
- }
8372
8347
  if (clientInput.quizContext?.originalCategory) {
8373
8348
  const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
8374
8349
  const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
@@ -8376,24 +8351,23 @@ async function generateMatchingQuestion(clientInput, apiKey) {
8376
8351
  throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
8377
8352
  }
8378
8353
  }
8379
- const finalPrompts = [];
8380
8354
  const finalOptions = [];
8381
- const finalCorrectAnswerMap = [];
8382
- aiGeneratedContent.correctPairs.forEach((pair2) => {
8383
- const promptId = generateUniqueId("m_p_");
8384
- const optionId = generateUniqueId("m_o_");
8385
- finalPrompts.push({ id: promptId, content: pair2.promptText });
8386
- finalOptions.push({ id: optionId, content: pair2.optionText });
8387
- finalCorrectAnswerMap.push({ promptId, optionId });
8355
+ const tempIdToFinalIdMap = {};
8356
+ aiGeneratedContent.options.forEach((aiOption) => {
8357
+ const finalId = generateUniqueId("opt_");
8358
+ finalOptions.push({ id: finalId, text: aiOption.text });
8359
+ tempIdToFinalIdMap[aiOption.tempId] = finalId;
8388
8360
  });
8361
+ const finalCorrectAnswerId = tempIdToFinalIdMap[aiGeneratedContent.correctTempOptionId];
8362
+ if (!finalCorrectAnswerId) {
8363
+ throw new Error(`Correct option ID '${aiGeneratedContent.correctTempOptionId}' is invalid`);
8364
+ }
8389
8365
  const completeQuestion = {
8390
- id: generateUniqueId("match_ai_"),
8391
- questionType: "matching",
8366
+ id: generateUniqueId("mcq_ai_"),
8367
+ questionType: "multiple_choice",
8392
8368
  prompt: aiGeneratedContent.prompt,
8393
- prompts: finalPrompts,
8394
8369
  options: finalOptions,
8395
- correctAnswerMap: finalCorrectAnswerMap,
8396
- shuffleOptions: clientInput.shuffleOptions,
8370
+ correctAnswerId: finalCorrectAnswerId,
8397
8371
  explanation: aiGeneratedContent.explanation,
8398
8372
  points: aiGeneratedContent.points,
8399
8373
  topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
@@ -8405,10 +8379,10 @@ async function generateMatchingQuestion(clientInput, apiKey) {
8405
8379
  category: clientInput.quizContext?.originalCategory,
8406
8380
  imageUrl: clientInput.imageUrl
8407
8381
  };
8408
- const validatedQuestion = MatchingQuestionZodSchema.parse(completeQuestion);
8382
+ const validatedQuestion = MultipleChoiceQuestionZodSchema.parse(completeQuestion);
8409
8383
  attemptResults.push({ success: true, duration, promptLength: promptText.length, responseLength: rawText.length, promptHash });
8410
8384
  console.log(`
8411
- \u2705 Matching generation successful on attempt ${attempt} (${duration}ms)`);
8385
+ \u2705 MCQ generation successful on attempt ${attempt} (${duration}ms)`);
8412
8386
  if (attempt > 1) DebugLogger.logAttemptSummary(attemptResults);
8413
8387
  return { question: validatedQuestion };
8414
8388
  } catch (error) {
@@ -8424,84 +8398,83 @@ async function generateMatchingQuestion(clientInput, apiKey) {
8424
8398
  }
8425
8399
  }
8426
8400
  DebugLogger.logAttemptSummary(attemptResults);
8427
- const errorMessage = `Failed to generate Matching question after ${MAX_RETRY_ATTEMPTS2} attempts. Last error: ${lastError?.message}`;
8401
+ const errorMessage = `Failed to generate MCQ question after ${MAX_RETRY_ATTEMPTS2} attempts. Last error: ${lastError?.message}`;
8428
8402
  console.error("\n\u274C Final Result: FAILED");
8429
8403
  console.error(errorMessage);
8430
8404
  return { error: errorMessage };
8431
8405
  }
8432
8406
  BaseQuestionGenerationClientInputSchema.extend({
8433
- numberOfOptions: zod.z.number().int().min(2).max(6).optional().default(4)
8407
+ numberOfOptions: zod.z.number().int().min(2).max(8).optional().default(5),
8408
+ minCorrectAnswers: zod.z.number().int().min(1).optional().default(2),
8409
+ maxCorrectAnswers: zod.z.number().int().min(1).optional().default(3)
8434
8410
  });
8435
- var AIMCQOutputFieldsSchema = zod.z.object({
8436
- prompt: zod.z.string().describe("The question statement itself."),
8437
- options: zod.z.array(
8438
- zod.z.object({
8439
- tempId: zod.z.string().describe("A temporary, unique identifier for this option (e.g., 'A', 'B', '1', '2')."),
8440
- text: zod.z.string().describe("The text content of this answer option.")
8441
- })
8442
- ).min(2).max(6),
8443
- correctTempOptionId: zod.z.string().describe("The temporary ID of the correct option from the generated options array."),
8444
- explanation: zod.z.string().optional().describe("A brief explanation of why the answer is correct."),
8411
+ var AIMRQOutputFieldsSchema = zod.z.object({
8412
+ prompt: zod.z.string(),
8413
+ options: zod.z.array(zod.z.object({ tempId: zod.z.string(), text: zod.z.string() })).min(2).max(8),
8414
+ correctTempOptionIds: zod.z.array(zod.z.string()).min(1),
8415
+ explanation: zod.z.string().optional(),
8445
8416
  points: zod.z.number().optional().default(10),
8446
8417
  difficulty: zod.z.enum(["easy", "medium", "hard"]).optional(),
8447
8418
  topic: zod.z.string().optional(),
8448
8419
  verifiedCategory: zod.z.string().optional().describe("The category this question actually addresses.")
8449
8420
  });
8450
8421
 
8451
- // src/ai/flows/question-gen/generate-mcq-question.ts
8422
+ // src/ai/flows/question-gen/generate-mrq-question.ts
8452
8423
  var MAX_RETRY_ATTEMPTS3 = 3;
8453
8424
  var RETRY_DELAY_MS3 = 3e3;
8454
8425
  function buildEnhancedPrompt3(clientInput, attemptNumber) {
8455
- const { quizContext, language: language3, difficulty, numberOfOptions, imageUrl } = clientInput;
8426
+ const { quizContext, language: language3, difficulty, numberOfOptions, minCorrectAnswers, maxCorrectAnswers, imageUrl } = clientInput;
8456
8427
  const category = quizContext?.originalCategory || "the specified technical category";
8457
8428
  const attemptInfo = attemptNumber > 1 ? `
8458
8429
  ## DEBUG INFO - This is attempt #${attemptNumber}
8459
- Previous attempts failed...
8430
+ Previous attempts failed due to validation errors. Pay close attention to the number of correct answers and the JSON schema.
8460
8431
 
8461
8432
  ` : "";
8462
8433
  const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and options must be directly related to the content of this image.` : "";
8463
8434
  const contextStrings = [
8464
- `**Required Category:** ${category}`,
8435
+ `**Required Category:** ${category} (This is the ONLY language to be used)`,
8465
8436
  quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
8466
8437
  imageContextInstruction,
8467
8438
  quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
8468
- quizContext?.targetMisconception && `**Target Misconception:** Use this to create plausible incorrect answers: "${quizContext.targetMisconception}"`,
8439
+ quizContext?.targetMisconception && `**Target Misconception:** Use this to create plausible incorrect answers (distractors). The misconception is: "${quizContext.targetMisconception}"`,
8469
8440
  quizContext?.difficultyReason && `**Pedagogical Reason:** ${quizContext.difficultyReason}`
8470
8441
  ].filter(Boolean).map((s2) => `- ${s2}`).join("\n");
8471
8442
  const exampleJson = JSON.stringify({
8472
- prompt: `In ${category}, what is the primary purpose of the 'guard' statement?`,
8443
+ prompt: "Which of the following are considered programming paradigms?",
8473
8444
  options: [
8474
- { tempId: "A", text: "To execute a block of code repeatedly." },
8475
- { tempId: "B", text: "To define a new custom data type." },
8476
- { tempId: "C", text: "To exit a scope early if a condition is not met." },
8477
- { tempId: "D", text: "To handle errors thrown by a function." }
8445
+ { "tempId": "A", "text": "Object-Oriented" },
8446
+ { "tempId": "B", "text": "Assembly" },
8447
+ { "tempId": "C", "text": "Functional" },
8448
+ { "tempId": "D", "text": "Procedural" },
8449
+ { "tempId": "E", "text": "Middleware" }
8478
8450
  ],
8479
- correctTempOptionId: "C",
8480
- explanation: `The 'guard' statement in ${category} provides an early exit from a scope (like a function) if a condition is false, enhancing code readability by handling required conditions upfront.`,
8451
+ correctTempOptionIds: ["A", "C", "D"],
8452
+ explanation: "Object-Oriented, Functional, and Procedural are all major programming paradigms. Assembly is a low-level language, and Middleware is a type of software, not a paradigm.",
8481
8453
  points: 10,
8482
- difficulty: "easy",
8483
- topic: `Control Flow in ${category}`,
8454
+ difficulty: "medium",
8455
+ topic: "Programming Paradigms",
8484
8456
  verifiedCategory: category
8485
8457
  }, null, 2);
8486
8458
  return `${attemptInfo}You are an expert Question Author for advanced technical education, specializing in the programming language: ${category}.
8487
- Your sole mission is to create a high-quality, technically accurate Multiple Choice Question. You must adhere to the following rules at all times.
8459
+ Your sole mission is to create a high-quality, technically accurate Multiple Response Question. You must adhere to the following rules at all times.
8488
8460
 
8489
8461
  ## Core Rules (Non-negotiable)
8490
- 1. **Category Purity:** The question, options, and explanation MUST be exclusively about **${category}**. Do NOT mention or use syntax from other languages.
8462
+ 1. **Category Purity:** The question, options, and explanation MUST be exclusively about **${category}**.
8491
8463
  2. **Context Adherence:** The question's content must directly align with all provided context.
8492
- 3. **Format Integrity:** You MUST return ONLY a single, valid JSON object that strictly follows the provided schema. Do not include any extra text, comments, or markdown formatting.
8464
+ 3. **Format Integrity:** You MUST return ONLY a single, valid JSON object that strictly follows the provided schema. Do not include any extra text or comments.
8493
8465
 
8494
8466
  ## CRITICAL CONTEXT FOR THIS QUESTION
8495
8467
  ${contextStrings}
8496
8468
 
8497
8469
  ## Task: Generate the Question
8498
- Based on all the rules and context above, generate a single Multiple Choice Question.
8470
+ Based on all the rules and context above, generate a single Multiple Response Question.
8499
8471
 
8500
8472
  ### Input Parameters
8501
8473
  - **Topic for Question:** ${quizContext?.plannedTopic || "General"}
8502
8474
  - **Language for Text:** ${language3}
8503
8475
  - **Difficulty Level:** ${difficulty}
8504
- - **Number of Options:** ${numberOfOptions}
8476
+ - **Number of Options:** Generate exactly ${numberOfOptions} options.
8477
+ - **Number of Correct Answers:** The 'correctTempOptionIds' array MUST contain between ${minCorrectAnswers} and ${maxCorrectAnswers} valid IDs from the options you generate.
8505
8478
 
8506
8479
  ### Required JSON Output Format
8507
8480
  Your response must be ONLY the JSON object, matching this exact structure and field names.
@@ -8510,7 +8483,13 @@ ${exampleJson}
8510
8483
 
8511
8484
  Now, generate the JSON for the requested question.`;
8512
8485
  }
8513
- async function generateMCQQuestion(clientInput, apiKey) {
8486
+ async function generateMRQQuestion(clientInput, apiKey) {
8487
+ if (clientInput.minCorrectAnswers > clientInput.maxCorrectAnswers) {
8488
+ return { error: `Invalid input: minCorrectAnswers (${clientInput.minCorrectAnswers}) cannot be greater than maxCorrectAnswers (${clientInput.maxCorrectAnswers}).` };
8489
+ }
8490
+ if (clientInput.maxCorrectAnswers >= clientInput.numberOfOptions) {
8491
+ return { error: `Invalid input: maxCorrectAnswers (${clientInput.maxCorrectAnswers}) must be less than the total numberOfOptions (${clientInput.numberOfOptions}).` };
8492
+ }
8514
8493
  const ai = new genai.GoogleGenAI({ apiKey });
8515
8494
  const model = "gemini-2.5-flash";
8516
8495
  const config2 = {
@@ -8549,8 +8528,15 @@ async function generateMCQQuestion(clientInput, apiKey) {
8549
8528
  }
8550
8529
  const parsedJson = JSON.parse(rawText);
8551
8530
  DebugLogger.logValidation(attempt, "JSON Parsed Successfully", parsedJson);
8552
- const aiGeneratedContent = AIMCQOutputFieldsSchema.parse(parsedJson);
8531
+ const aiGeneratedContent = AIMRQOutputFieldsSchema.parse(parsedJson);
8553
8532
  DebugLogger.logValidation(attempt, "Zod Schema Validated", aiGeneratedContent);
8533
+ if (aiGeneratedContent.options.length !== clientInput.numberOfOptions) {
8534
+ throw new Error(`AI generated ${aiGeneratedContent.options.length} options, but ${clientInput.numberOfOptions} were required.`);
8535
+ }
8536
+ const correctCount = aiGeneratedContent.correctTempOptionIds.length;
8537
+ if (correctCount < clientInput.minCorrectAnswers || correctCount > clientInput.maxCorrectAnswers) {
8538
+ throw new Error(`AI provided ${correctCount} correct answers, which is outside the required range of ${clientInput.minCorrectAnswers}-${clientInput.maxCorrectAnswers}.`);
8539
+ }
8554
8540
  if (clientInput.quizContext?.originalCategory) {
8555
8541
  const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
8556
8542
  const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
@@ -8560,25 +8546,29 @@ async function generateMCQQuestion(clientInput, apiKey) {
8560
8546
  }
8561
8547
  const finalOptions = [];
8562
8548
  const tempIdToFinalIdMap = {};
8549
+ const allTempIds = /* @__PURE__ */ new Set();
8563
8550
  aiGeneratedContent.options.forEach((aiOption) => {
8564
- const finalId = generateUniqueId("opt_");
8551
+ const finalId = generateUniqueId("opt_mr_");
8565
8552
  finalOptions.push({ id: finalId, text: aiOption.text });
8566
8553
  tempIdToFinalIdMap[aiOption.tempId] = finalId;
8554
+ allTempIds.add(aiOption.tempId);
8555
+ });
8556
+ const finalCorrectAnswerIds = aiGeneratedContent.correctTempOptionIds.map((tempId) => {
8557
+ if (!allTempIds.has(tempId)) {
8558
+ throw new Error(`AI provided an invalid correctTempOptionId ('${tempId}') which does not exist in the generated options.`);
8559
+ }
8560
+ return tempIdToFinalIdMap[tempId];
8567
8561
  });
8568
- const finalCorrectAnswerId = tempIdToFinalIdMap[aiGeneratedContent.correctTempOptionId];
8569
- if (!finalCorrectAnswerId) {
8570
- throw new Error(`Correct option ID '${aiGeneratedContent.correctTempOptionId}' is invalid`);
8571
- }
8572
8562
  const completeQuestion = {
8573
- id: generateUniqueId("mcq_ai_"),
8574
- questionType: "multiple_choice",
8563
+ id: generateUniqueId("mrq_ai_"),
8564
+ questionType: "multiple_response",
8575
8565
  prompt: aiGeneratedContent.prompt,
8576
8566
  options: finalOptions,
8577
- correctAnswerId: finalCorrectAnswerId,
8567
+ correctAnswerIds: finalCorrectAnswerIds,
8578
8568
  explanation: aiGeneratedContent.explanation,
8579
8569
  points: aiGeneratedContent.points,
8580
8570
  topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
8581
- difficulty: clientInput.difficulty,
8571
+ difficulty: aiGeneratedContent.difficulty || clientInput.difficulty,
8582
8572
  contextCode: clientInput.quizContext?.plannedContextId,
8583
8573
  bloomLevel: clientInput.quizContext?.plannedBloomLevel,
8584
8574
  learningObjective: clientInput.quizContext?.originalLoId,
@@ -8586,10 +8576,10 @@ async function generateMCQQuestion(clientInput, apiKey) {
8586
8576
  category: clientInput.quizContext?.originalCategory,
8587
8577
  imageUrl: clientInput.imageUrl
8588
8578
  };
8589
- const validatedQuestion = MultipleChoiceQuestionZodSchema.parse(completeQuestion);
8579
+ const validatedQuestion = MultipleResponseQuestionZodSchema.parse(completeQuestion);
8590
8580
  attemptResults.push({ success: true, duration, promptLength: promptText.length, responseLength: rawText.length, promptHash });
8591
8581
  console.log(`
8592
- \u2705 MCQ generation successful on attempt ${attempt} (${duration}ms)`);
8582
+ \u2705 MRQ generation successful on attempt ${attempt} (${duration}ms)`);
8593
8583
  if (attempt > 1) DebugLogger.logAttemptSummary(attemptResults);
8594
8584
  return { question: validatedQuestion };
8595
8585
  } catch (error) {
@@ -8605,102 +8595,85 @@ async function generateMCQQuestion(clientInput, apiKey) {
8605
8595
  }
8606
8596
  }
8607
8597
  DebugLogger.logAttemptSummary(attemptResults);
8608
- const errorMessage = `Failed to generate MCQ question after ${MAX_RETRY_ATTEMPTS3} attempts. Last error: ${lastError?.message}`;
8598
+ const errorMessage = `Failed to generate MRQ question after ${MAX_RETRY_ATTEMPTS3} attempts. Last error: ${lastError?.message}`;
8609
8599
  console.error("\n\u274C Final Result: FAILED");
8610
8600
  console.error(errorMessage);
8611
8601
  return { error: errorMessage };
8612
8602
  }
8613
8603
  BaseQuestionGenerationClientInputSchema.extend({
8614
- numberOfOptions: zod.z.number().int().min(2).max(8).optional().default(5),
8615
- minCorrectAnswers: zod.z.number().int().min(1).optional().default(2),
8616
- maxCorrectAnswers: zod.z.number().int().min(1).optional().default(3)
8604
+ isCaseSensitive: zod.z.boolean().optional().default(false)
8617
8605
  });
8618
- var AIMRQOutputFieldsSchema = zod.z.object({
8619
- prompt: zod.z.string(),
8620
- options: zod.z.array(zod.z.object({ tempId: zod.z.string(), text: zod.z.string() })).min(2).max(8),
8621
- correctTempOptionIds: zod.z.array(zod.z.string()).min(1),
8606
+ var AIShortAnswerOutputFieldsSchema = zod.z.object({
8607
+ prompt: zod.z.string().describe("The question text that prompts the user for a short answer."),
8608
+ acceptedAnswers: zod.z.array(zod.z.string().min(1)).min(1).describe("An array of one or more acceptable short answers. Include common variations if applicable."),
8609
+ // isCaseSensitive không cần thiết ở đây, chúng ta sẽ quản lý nó ở phía client
8622
8610
  explanation: zod.z.string().optional(),
8623
8611
  points: zod.z.number().optional().default(10),
8624
8612
  difficulty: zod.z.enum(["easy", "medium", "hard"]).optional(),
8625
8613
  topic: zod.z.string().optional(),
8626
- verifiedCategory: zod.z.string().optional().describe("The category this question actually addresses.")
8614
+ verifiedCategory: zod.z.string().optional()
8615
+ // Thêm để xác thực
8627
8616
  });
8628
8617
 
8629
- // src/ai/flows/question-gen/generate-mrq-question.ts
8618
+ // src/ai/flows/question-gen/generate-short-answer-question.ts
8630
8619
  var MAX_RETRY_ATTEMPTS4 = 3;
8631
8620
  var RETRY_DELAY_MS4 = 3e3;
8632
8621
  function buildEnhancedPrompt4(clientInput, attemptNumber) {
8633
- const { quizContext, language: language3, difficulty, numberOfOptions, minCorrectAnswers, maxCorrectAnswers, imageUrl } = clientInput;
8622
+ const { quizContext, language: language3, difficulty, imageUrl } = clientInput;
8634
8623
  const category = quizContext?.originalCategory || "the specified technical category";
8635
8624
  const attemptInfo = attemptNumber > 1 ? `
8636
8625
  ## DEBUG INFO - This is attempt #${attemptNumber}
8637
- Previous attempts failed due to validation errors. Pay close attention to the number of correct answers and the JSON schema.
8626
+ Previous attempts failed. Ensure 'acceptedAnswers' is a non-empty array of strings and the JSON is valid.
8638
8627
 
8639
8628
  ` : "";
8640
- const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and options must be directly related to the content of this image.` : "";
8629
+ const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and its short answer must be directly related to the content of this image.` : "";
8641
8630
  const contextStrings = [
8642
- `**Required Category:** ${category} (This is the ONLY language to be used)`,
8631
+ `**Required Category:** ${category}`,
8643
8632
  quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
8644
8633
  imageContextInstruction,
8645
8634
  quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
8646
- quizContext?.targetMisconception && `**Target Misconception:** Use this to create plausible incorrect answers (distractors). The misconception is: "${quizContext.targetMisconception}"`,
8647
- quizContext?.difficultyReason && `**Pedagogical Reason:** ${quizContext.difficultyReason}`
8635
+ quizContext?.targetMisconception && `**Target Misconception:** The question should require an answer that corrects this specific misconception: "${quizContext.targetMisconception}"`
8648
8636
  ].filter(Boolean).map((s2) => `- ${s2}`).join("\n");
8649
8637
  const exampleJson = JSON.stringify({
8650
- prompt: "Which of the following are considered programming paradigms?",
8651
- options: [
8652
- { "tempId": "A", "text": "Object-Oriented" },
8653
- { "tempId": "B", "text": "Assembly" },
8654
- { "tempId": "C", "text": "Functional" },
8655
- { "tempId": "D", "text": "Procedural" },
8656
- { "tempId": "E", "text": "Middleware" }
8657
- ],
8658
- correctTempOptionIds: ["A", "C", "D"],
8659
- explanation: "Object-Oriented, Functional, and Procedural are all major programming paradigms. Assembly is a low-level language, and Middleware is a type of software, not a paradigm.",
8638
+ prompt: "In Swift, what keyword is used to declare a constant?",
8639
+ acceptedAnswers: ["let"],
8640
+ explanation: "The 'let' keyword is used to declare constants, which are values that cannot be changed after they are set. 'var' is used for variables.",
8660
8641
  points: 10,
8661
- difficulty: "medium",
8662
- topic: "Programming Paradigms",
8642
+ difficulty: "easy",
8643
+ topic: "Swift Constants",
8663
8644
  verifiedCategory: category
8664
8645
  }, null, 2);
8665
- return `${attemptInfo}You are an expert Question Author for advanced technical education, specializing in the programming language: ${category}.
8666
- Your sole mission is to create a high-quality, technically accurate Multiple Response Question. You must adhere to the following rules at all times.
8646
+ return `${attemptInfo}You are an expert Question Author for advanced technical education, specializing in: ${category}.
8647
+ Your mission is to create a high-quality, technically accurate Short Answer Question.
8667
8648
 
8668
8649
  ## Core Rules (Non-negotiable)
8669
- 1. **Category Purity:** The question, options, and explanation MUST be exclusively about **${category}**.
8670
- 2. **Context Adherence:** The question's content must directly align with all provided context.
8671
- 3. **Format Integrity:** You MUST return ONLY a single, valid JSON object that strictly follows the provided schema. Do not include any extra text or comments.
8650
+ 1. **Category Purity:** The question MUST be exclusively about **${category}**.
8651
+ 2. **Objective Answer:** The question must have a short, factual, and objective answer. Avoid questions that are subjective or require long explanations.
8652
+ 3. **Schema Integrity:** The response MUST be ONLY a single, valid JSON object.
8672
8653
 
8673
8654
  ## CRITICAL CONTEXT FOR THIS QUESTION
8674
8655
  ${contextStrings}
8675
8656
 
8676
8657
  ## Task: Generate the Question
8677
- Based on all the rules and context above, generate a single Multiple Response Question.
8658
+ Based on all the rules and context above, generate a single Short Answer Question.
8678
8659
 
8679
8660
  ### Input Parameters
8680
8661
  - **Topic for Question:** ${quizContext?.plannedTopic || "General"}
8681
8662
  - **Language for Text:** ${language3}
8682
8663
  - **Difficulty Level:** ${difficulty}
8683
- - **Number of Options:** Generate exactly ${numberOfOptions} options.
8684
- - **Number of Correct Answers:** The 'correctTempOptionIds' array MUST contain between ${minCorrectAnswers} and ${maxCorrectAnswers} valid IDs from the options you generate.
8685
8664
 
8686
8665
  ### Required JSON Output Format
8687
- Your response must be ONLY the JSON object, matching this exact structure and field names.
8666
+ Your response must be ONLY the JSON object, matching this exact structure:
8688
8667
 
8689
8668
  ${exampleJson}
8690
8669
 
8691
8670
  Now, generate the JSON for the requested question.`;
8692
8671
  }
8693
- async function generateMRQQuestion(clientInput, apiKey) {
8694
- if (clientInput.minCorrectAnswers > clientInput.maxCorrectAnswers) {
8695
- return { error: `Invalid input: minCorrectAnswers (${clientInput.minCorrectAnswers}) cannot be greater than maxCorrectAnswers (${clientInput.maxCorrectAnswers}).` };
8696
- }
8697
- if (clientInput.maxCorrectAnswers >= clientInput.numberOfOptions) {
8698
- return { error: `Invalid input: maxCorrectAnswers (${clientInput.maxCorrectAnswers}) must be less than the total numberOfOptions (${clientInput.numberOfOptions}).` };
8699
- }
8672
+ async function generateShortAnswerQuestion(clientInput, apiKey) {
8700
8673
  const ai = new genai.GoogleGenAI({ apiKey });
8701
8674
  const model = "gemini-2.5-flash";
8702
8675
  const config2 = {
8703
- temperature: 0.8,
8676
+ temperature: 0.5,
8704
8677
  responseMimeType: "application/json",
8705
8678
  thinkingConfig: {
8706
8679
  thinkingBudget: 4e3
@@ -8721,29 +8694,16 @@ async function generateMRQQuestion(clientInput, apiKey) {
8721
8694
  parts.unshift(imagePart);
8722
8695
  }
8723
8696
  const contents = [{ role: "user", parts }];
8724
- const aiResult = await ai.models.generateContent({
8725
- model,
8726
- config: config2,
8727
- contents
8728
- });
8697
+ const aiResult = await ai.models.generateContent({ model, config: config2, contents });
8729
8698
  const response = aiResult;
8730
8699
  const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
8731
8700
  const duration = Date.now() - startTime;
8732
8701
  DebugLogger.logResponse(attempt, rawText);
8733
- if (!rawText) {
8734
- throw new Error("AI returned an empty response.");
8735
- }
8702
+ if (!rawText) throw new Error("AI returned an empty response.");
8736
8703
  const parsedJson = JSON.parse(rawText);
8737
8704
  DebugLogger.logValidation(attempt, "JSON Parsed Successfully", parsedJson);
8738
- const aiGeneratedContent = AIMRQOutputFieldsSchema.parse(parsedJson);
8705
+ const aiGeneratedContent = AIShortAnswerOutputFieldsSchema.parse(parsedJson);
8739
8706
  DebugLogger.logValidation(attempt, "Zod Schema Validated", aiGeneratedContent);
8740
- if (aiGeneratedContent.options.length !== clientInput.numberOfOptions) {
8741
- throw new Error(`AI generated ${aiGeneratedContent.options.length} options, but ${clientInput.numberOfOptions} were required.`);
8742
- }
8743
- const correctCount = aiGeneratedContent.correctTempOptionIds.length;
8744
- if (correctCount < clientInput.minCorrectAnswers || correctCount > clientInput.maxCorrectAnswers) {
8745
- throw new Error(`AI provided ${correctCount} correct answers, which is outside the required range of ${clientInput.minCorrectAnswers}-${clientInput.maxCorrectAnswers}.`);
8746
- }
8747
8707
  if (clientInput.quizContext?.originalCategory) {
8748
8708
  const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
8749
8709
  const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
@@ -8751,31 +8711,16 @@ async function generateMRQQuestion(clientInput, apiKey) {
8751
8711
  throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
8752
8712
  }
8753
8713
  }
8754
- const finalOptions = [];
8755
- const tempIdToFinalIdMap = {};
8756
- const allTempIds = /* @__PURE__ */ new Set();
8757
- aiGeneratedContent.options.forEach((aiOption) => {
8758
- const finalId = generateUniqueId("opt_mr_");
8759
- finalOptions.push({ id: finalId, text: aiOption.text });
8760
- tempIdToFinalIdMap[aiOption.tempId] = finalId;
8761
- allTempIds.add(aiOption.tempId);
8762
- });
8763
- const finalCorrectAnswerIds = aiGeneratedContent.correctTempOptionIds.map((tempId) => {
8764
- if (!allTempIds.has(tempId)) {
8765
- throw new Error(`AI provided an invalid correctTempOptionId ('${tempId}') which does not exist in the generated options.`);
8766
- }
8767
- return tempIdToFinalIdMap[tempId];
8768
- });
8769
8714
  const completeQuestion = {
8770
- id: generateUniqueId("mrq_ai_"),
8771
- questionType: "multiple_response",
8715
+ id: generateUniqueId("saq_ai_"),
8716
+ questionType: "short_answer",
8772
8717
  prompt: aiGeneratedContent.prompt,
8773
- options: finalOptions,
8774
- correctAnswerIds: finalCorrectAnswerIds,
8718
+ acceptedAnswers: aiGeneratedContent.acceptedAnswers,
8719
+ isCaseSensitive: clientInput.isCaseSensitive,
8775
8720
  explanation: aiGeneratedContent.explanation,
8776
8721
  points: aiGeneratedContent.points,
8777
8722
  topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
8778
- difficulty: aiGeneratedContent.difficulty || clientInput.difficulty,
8723
+ difficulty: clientInput.difficulty,
8779
8724
  contextCode: clientInput.quizContext?.plannedContextId,
8780
8725
  bloomLevel: clientInput.quizContext?.plannedBloomLevel,
8781
8726
  learningObjective: clientInput.quizContext?.originalLoId,
@@ -8783,10 +8728,10 @@ async function generateMRQQuestion(clientInput, apiKey) {
8783
8728
  category: clientInput.quizContext?.originalCategory,
8784
8729
  imageUrl: clientInput.imageUrl
8785
8730
  };
8786
- const validatedQuestion = MultipleResponseQuestionZodSchema.parse(completeQuestion);
8731
+ const validatedQuestion = ShortAnswerQuestionZodSchema.parse(completeQuestion);
8787
8732
  attemptResults.push({ success: true, duration, promptLength: promptText.length, responseLength: rawText.length, promptHash });
8788
8733
  console.log(`
8789
- \u2705 MRQ generation successful on attempt ${attempt} (${duration}ms)`);
8734
+ \u2705 Short Answer generation successful on attempt ${attempt} (${duration}ms)`);
8790
8735
  if (attempt > 1) DebugLogger.logAttemptSummary(attemptResults);
8791
8736
  return { question: validatedQuestion };
8792
8737
  } catch (error) {
@@ -8802,7 +8747,7 @@ async function generateMRQQuestion(clientInput, apiKey) {
8802
8747
  }
8803
8748
  }
8804
8749
  DebugLogger.logAttemptSummary(attemptResults);
8805
- const errorMessage = `Failed to generate MRQ question after ${MAX_RETRY_ATTEMPTS4} attempts. Last error: ${lastError?.message}`;
8750
+ const errorMessage = `Failed to generate Short Answer question after ${MAX_RETRY_ATTEMPTS4} attempts. Last error: ${lastError?.message}`;
8806
8751
  console.error("\n\u274C Final Result: FAILED");
8807
8752
  console.error(errorMessage);
8808
8753
  return { error: errorMessage };
@@ -8988,13 +8933,17 @@ async function generateNumericQuestion(clientInput, apiKey) {
8988
8933
  return { error: errorMessage };
8989
8934
  }
8990
8935
  BaseQuestionGenerationClientInputSchema.extend({
8991
- numberOfItems: zod.z.number().int().min(2).max(10).optional().default(4)
8936
+ numberOfBlanks: zod.z.number().int().min(1).max(5).optional().default(1),
8937
+ isCaseSensitive: zod.z.boolean().optional().default(false)
8992
8938
  });
8993
- var AISequenceOutputFieldsSchema = zod.z.object({
8994
- prompt: zod.z.string().describe("The instructional text for the user, e.g., 'Arrange the steps in the correct order.'"),
8995
- // Yêu cầu AI cung cấp các item dưới dạng một mảng duy nhất đã được sắp xếp đúng thứ tự
8996
- // Điều này đơn giản hóa logic và giảm rủi ro
8997
- itemsInCorrectOrder: zod.z.array(zod.z.string().min(1)).min(2).describe("An array of strings, with each string representing an item to be sequenced. The array itself MUST be in the correct final order."),
8939
+ var AIFillInTheBlanksOutputFieldsSchema = zod.z.object({
8940
+ prompt: zod.z.string().describe("The instructional text for the user, e.g., 'Fill in the blanks to complete the sentence.'"),
8941
+ // Yêu cầu AI trả về cấu trúc segments trực tiếp
8942
+ segments: zod.z.array(zod.z.object({
8943
+ type: zod.z.enum(["text", "blank"]),
8944
+ content: zod.z.string().optional().describe("The text content for a 'text' segment."),
8945
+ acceptedAnswers: zod.z.array(zod.z.string().min(1)).min(1).optional().describe("An array of correct answers for a 'blank' segment.")
8946
+ })).min(1).describe("An array of text and blank segments representing the question."),
8998
8947
  explanation: zod.z.string().optional(),
8999
8948
  points: zod.z.number().optional().default(10),
9000
8949
  difficulty: zod.z.enum(["easy", "medium", "hard"]).optional(),
@@ -9003,58 +8952,57 @@ var AISequenceOutputFieldsSchema = zod.z.object({
9003
8952
  // Thêm để xác thực
9004
8953
  });
9005
8954
 
9006
- // src/ai/flows/question-gen/generate-sequence-question.ts
8955
+ // src/ai/flows/question-gen/generate-fitb-question.ts
9007
8956
  var MAX_RETRY_ATTEMPTS6 = 3;
9008
8957
  var RETRY_DELAY_MS6 = 3e3;
9009
8958
  function buildEnhancedPrompt6(clientInput, attemptNumber) {
9010
- const { quizContext, language: language3, difficulty, numberOfItems, imageUrl } = clientInput;
8959
+ const { quizContext, language: language3, difficulty, numberOfBlanks, imageUrl } = clientInput;
9011
8960
  const category = quizContext?.originalCategory || "the specified technical category";
9012
8961
  const attemptInfo = attemptNumber > 1 ? `
9013
8962
  ## DEBUG INFO - This is attempt #${attemptNumber}
9014
- Previous attempts failed. Ensure the 'itemsInCorrectOrder' array has exactly the required number of items and the JSON is valid.
8963
+ Previous attempts failed. Pay strict attention to the JSON schema, especially the 'segments' array structure. Ensure 'blank' segments have 'acceptedAnswers' and 'text' segments have 'content'.
9015
8964
 
9016
8965
  ` : "";
9017
- const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The sequence of items must be directly related to the process or content shown in this image.` : "";
8966
+ const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and blanks must be directly related to the content of this image.` : "";
9018
8967
  const contextStrings = [
9019
8968
  `**Required Category:** ${category}`,
9020
8969
  quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
9021
8970
  imageContextInstruction,
9022
8971
  quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
9023
- quizContext?.targetMisconception && `**Target Misconception:** The sequence should clarify this specific process error: "${quizContext.targetMisconception}"`
8972
+ quizContext?.targetMisconception && `**Target Misconception:** Design the blank to test this specific point: "${quizContext.targetMisconception}"`
9024
8973
  ].filter(Boolean).map((s2) => `- ${s2}`).join("\n");
9025
8974
  const exampleJson = JSON.stringify({
9026
- prompt: "Arrange the steps to make a network request in Swift using URLSession.",
9027
- itemsInCorrectOrder: [
9028
- "Create a URL object.",
9029
- "Create a URLSessionDataTask with the URL.",
9030
- "Start the task by calling its resume() method.",
9031
- "Handle the completion handler with data, response, and error."
8975
+ prompt: "Complete the following Swift code snippet.",
8976
+ segments: [
8977
+ { "type": "text", "content": "To declare a new function in Swift, you use the `" },
8978
+ { "type": "blank", "acceptedAnswers": ["func"] },
8979
+ { "type": "text", "content": "` keyword." }
9032
8980
  ],
9033
- explanation: "This is the fundamental sequence for a basic data task in URLSession.",
8981
+ explanation: "The 'func' keyword is used to declare a function in the Swift programming language.",
9034
8982
  points: 10,
9035
- difficulty: "medium",
9036
- topic: "Swift Networking",
8983
+ difficulty: "easy",
8984
+ topic: "Swift Function Declaration",
9037
8985
  verifiedCategory: category
9038
8986
  }, null, 2);
9039
8987
  return `${attemptInfo}You are an expert Question Author for advanced technical education, specializing in: ${category}.
9040
- Your mission is to create a high-quality, technically accurate Sequence Question.
8988
+ Your mission is to create a high-quality, technically accurate Fill-in-the-Blanks Question.
9041
8989
 
9042
8990
  ## Core Rules (Non-negotiable)
9043
8991
  1. **Category Purity:** The question MUST be exclusively about **${category}**.
9044
- 2. **Unambiguous Order:** The items MUST represent a clear, objective process, timeline, or order with only one correct sequence.
9045
- 3. **Schema Integrity:** The response MUST be ONLY a single, valid JSON object that strictly follows the provided schema.
8992
+ 2. **Schema Integrity:** The response MUST be ONLY a single, valid JSON object that strictly follows the provided schema.
8993
+ 3. **Logical Segments:** For 'blank' segments, you MUST provide 'acceptedAnswers'. For 'text' segments, you MUST provide 'content'. Do not mix them.
9046
8994
 
9047
8995
  ## CRITICAL CONTEXT FOR THIS QUESTION
9048
8996
  ${contextStrings}
9049
8997
 
9050
8998
  ## Task: Generate the Question
9051
- Based on all the rules and context above, generate a single Sequence Question.
8999
+ Based on all the rules and context above, generate a single Fill-in-the-Blanks Question.
9052
9000
 
9053
9001
  ### Input Parameters
9054
9002
  - **Topic for Question:** ${quizContext?.plannedTopic || "General"}
9055
9003
  - **Language for Text:** ${language3}
9056
9004
  - **Difficulty Level:** ${difficulty}
9057
- - **Number of Items:** Generate an array 'itemsInCorrectOrder' containing exactly ${numberOfItems} items. The array itself MUST be in the correct final sequence.
9005
+ - **Number of Blanks:** Generate exactly ${numberOfBlanks} segment(s) with type 'blank'.
9058
9006
 
9059
9007
  ### Required JSON Output Format
9060
9008
  Your response must be ONLY the JSON object, matching this exact structure:
@@ -9063,11 +9011,11 @@ ${exampleJson}
9063
9011
 
9064
9012
  Now, generate the JSON for the requested question.`;
9065
9013
  }
9066
- async function generateSequenceQuestion(clientInput, apiKey) {
9014
+ async function generateFillInTheBlanksQuestion(clientInput, apiKey) {
9067
9015
  const ai = new genai.GoogleGenAI({ apiKey });
9068
9016
  const model = "gemini-2.5-flash";
9069
9017
  const config2 = {
9070
- temperature: 0.7,
9018
+ temperature: 0.8,
9071
9019
  responseMimeType: "application/json",
9072
9020
  thinkingConfig: {
9073
9021
  thinkingBudget: 4e3
@@ -9096,11 +9044,20 @@ async function generateSequenceQuestion(clientInput, apiKey) {
9096
9044
  if (!rawText) throw new Error("AI returned an empty response.");
9097
9045
  const parsedJson = JSON.parse(rawText);
9098
9046
  DebugLogger.logValidation(attempt, "JSON Parsed Successfully", parsedJson);
9099
- const aiGeneratedContent = AISequenceOutputFieldsSchema.parse(parsedJson);
9047
+ const aiGeneratedContent = AIFillInTheBlanksOutputFieldsSchema.parse(parsedJson);
9100
9048
  DebugLogger.logValidation(attempt, "Zod Schema Validated", aiGeneratedContent);
9101
- if (aiGeneratedContent.itemsInCorrectOrder.length !== clientInput.numberOfItems) {
9102
- throw new Error(`AI generated ${aiGeneratedContent.itemsInCorrectOrder.length} items, but ${clientInput.numberOfItems} were required.`);
9049
+ const blankCount = aiGeneratedContent.segments.filter((s2) => s2.type === "blank").length;
9050
+ if (blankCount !== clientInput.numberOfBlanks) {
9051
+ throw new Error(`AI generated ${blankCount} blanks, but ${clientInput.numberOfBlanks} were required.`);
9103
9052
  }
9053
+ aiGeneratedContent.segments.forEach((segment, index3) => {
9054
+ if (segment.type === "blank" && (!segment.acceptedAnswers || segment.acceptedAnswers.length === 0)) {
9055
+ throw new Error(`Segment ${index3} is a 'blank' but is missing 'acceptedAnswers'.`);
9056
+ }
9057
+ if (segment.type === "text" && typeof segment.content !== "string") {
9058
+ throw new Error(`Segment ${index3} is 'text' but is missing 'content'.`);
9059
+ }
9060
+ });
9104
9061
  if (clientInput.quizContext?.originalCategory) {
9105
9062
  const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
9106
9063
  const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
@@ -9108,19 +9065,24 @@ async function generateSequenceQuestion(clientInput, apiKey) {
9108
9065
  throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
9109
9066
  }
9110
9067
  }
9111
- const finalItems = [];
9112
- const finalCorrectOrder = [];
9113
- aiGeneratedContent.itemsInCorrectOrder.forEach((content4) => {
9114
- const id2 = generateUniqueId("seqi_");
9115
- finalItems.push({ id: id2, content: content4 });
9116
- finalCorrectOrder.push(id2);
9068
+ const finalSegments = [];
9069
+ const finalAnswers = [];
9070
+ aiGeneratedContent.segments.forEach((segment) => {
9071
+ if (segment.type === "text") {
9072
+ finalSegments.push({ type: "text", content: segment.content });
9073
+ } else if (segment.type === "blank" && segment.acceptedAnswers) {
9074
+ const blankId = generateUniqueId("blank_");
9075
+ finalSegments.push({ type: "blank", id: blankId });
9076
+ finalAnswers.push({ blankId, acceptedValues: segment.acceptedAnswers });
9077
+ }
9117
9078
  });
9118
9079
  const completeQuestion = {
9119
- id: generateUniqueId("seq_ai_"),
9120
- questionType: "sequence",
9080
+ id: generateUniqueId("fitb_ai_"),
9081
+ questionType: "fill_in_the_blanks",
9121
9082
  prompt: aiGeneratedContent.prompt,
9122
- items: finalItems,
9123
- correctOrder: finalCorrectOrder,
9083
+ segments: finalSegments,
9084
+ answers: finalAnswers,
9085
+ isCaseSensitive: clientInput.isCaseSensitive,
9124
9086
  explanation: aiGeneratedContent.explanation,
9125
9087
  points: aiGeneratedContent.points,
9126
9088
  topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
@@ -9132,10 +9094,10 @@ async function generateSequenceQuestion(clientInput, apiKey) {
9132
9094
  category: clientInput.quizContext?.originalCategory,
9133
9095
  imageUrl: clientInput.imageUrl
9134
9096
  };
9135
- const validatedQuestion = SequenceQuestionZodSchema.parse(completeQuestion);
9097
+ const validatedQuestion = FillInTheBlanksQuestionZodSchema.parse(completeQuestion);
9136
9098
  attemptResults.push({ success: true, duration, promptLength: promptText.length, responseLength: rawText.length, promptHash });
9137
9099
  console.log(`
9138
- \u2705 Sequence generation successful on attempt ${attempt} (${duration}ms)`);
9100
+ \u2705 FITB generation successful on attempt ${attempt} (${duration}ms)`);
9139
9101
  if (attempt > 1) DebugLogger.logAttemptSummary(attemptResults);
9140
9102
  return { question: validatedQuestion };
9141
9103
  } catch (error) {
@@ -9151,18 +9113,19 @@ async function generateSequenceQuestion(clientInput, apiKey) {
9151
9113
  }
9152
9114
  }
9153
9115
  DebugLogger.logAttemptSummary(attemptResults);
9154
- const errorMessage = `Failed to generate Sequence question after ${MAX_RETRY_ATTEMPTS6} attempts. Last error: ${lastError?.message}`;
9116
+ const errorMessage = `Failed to generate FITB question after ${MAX_RETRY_ATTEMPTS6} attempts. Last error: ${lastError?.message}`;
9155
9117
  console.error("\n\u274C Final Result: FAILED");
9156
9118
  console.error(errorMessage);
9157
9119
  return { error: errorMessage };
9158
9120
  }
9159
9121
  BaseQuestionGenerationClientInputSchema.extend({
9160
- isCaseSensitive: zod.z.boolean().optional().default(false)
9122
+ numberOfItems: zod.z.number().int().min(2).max(10).optional().default(4)
9161
9123
  });
9162
- var AIShortAnswerOutputFieldsSchema = zod.z.object({
9163
- prompt: zod.z.string().describe("The question text that prompts the user for a short answer."),
9164
- acceptedAnswers: zod.z.array(zod.z.string().min(1)).min(1).describe("An array of one or more acceptable short answers. Include common variations if applicable."),
9165
- // isCaseSensitive không cần thiết đây, chúng ta sẽ quản lý nó ở phía client
9124
+ var AISequenceOutputFieldsSchema = zod.z.object({
9125
+ prompt: zod.z.string().describe("The instructional text for the user, e.g., 'Arrange the steps in the correct order.'"),
9126
+ // Yêu cầu AI cung cấp các item dưới dạng một mảng duy nhất đã được sắp xếp đúng thứ tự
9127
+ // Điều này đơn giản hóa logic giảm rủi ro
9128
+ itemsInCorrectOrder: zod.z.array(zod.z.string().min(1)).min(2).describe("An array of strings, with each string representing an item to be sequenced. The array itself MUST be in the correct final order."),
9166
9129
  explanation: zod.z.string().optional(),
9167
9130
  points: zod.z.number().optional().default(10),
9168
9131
  difficulty: zod.z.enum(["easy", "medium", "hard"]).optional(),
@@ -9171,52 +9134,58 @@ var AIShortAnswerOutputFieldsSchema = zod.z.object({
9171
9134
  // Thêm để xác thực
9172
9135
  });
9173
9136
 
9174
- // src/ai/flows/question-gen/generate-short-answer-question.ts
9137
+ // src/ai/flows/question-gen/generate-sequence-question.ts
9175
9138
  var MAX_RETRY_ATTEMPTS7 = 3;
9176
9139
  var RETRY_DELAY_MS7 = 3e3;
9177
9140
  function buildEnhancedPrompt7(clientInput, attemptNumber) {
9178
- const { quizContext, language: language3, difficulty, imageUrl } = clientInput;
9141
+ const { quizContext, language: language3, difficulty, numberOfItems, imageUrl } = clientInput;
9179
9142
  const category = quizContext?.originalCategory || "the specified technical category";
9180
9143
  const attemptInfo = attemptNumber > 1 ? `
9181
9144
  ## DEBUG INFO - This is attempt #${attemptNumber}
9182
- Previous attempts failed. Ensure 'acceptedAnswers' is a non-empty array of strings and the JSON is valid.
9145
+ Previous attempts failed. Ensure the 'itemsInCorrectOrder' array has exactly the required number of items and the JSON is valid.
9183
9146
 
9184
9147
  ` : "";
9185
- const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and its short answer must be directly related to the content of this image.` : "";
9148
+ const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The sequence of items must be directly related to the process or content shown in this image.` : "";
9186
9149
  const contextStrings = [
9187
9150
  `**Required Category:** ${category}`,
9188
9151
  quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
9189
9152
  imageContextInstruction,
9190
9153
  quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
9191
- quizContext?.targetMisconception && `**Target Misconception:** The question should require an answer that corrects this specific misconception: "${quizContext.targetMisconception}"`
9154
+ quizContext?.targetMisconception && `**Target Misconception:** The sequence should clarify this specific process error: "${quizContext.targetMisconception}"`
9192
9155
  ].filter(Boolean).map((s2) => `- ${s2}`).join("\n");
9193
9156
  const exampleJson = JSON.stringify({
9194
- prompt: "In Swift, what keyword is used to declare a constant?",
9195
- acceptedAnswers: ["let"],
9196
- explanation: "The 'let' keyword is used to declare constants, which are values that cannot be changed after they are set. 'var' is used for variables.",
9157
+ prompt: "Arrange the steps to make a network request in Swift using URLSession.",
9158
+ itemsInCorrectOrder: [
9159
+ "Create a URL object.",
9160
+ "Create a URLSessionDataTask with the URL.",
9161
+ "Start the task by calling its resume() method.",
9162
+ "Handle the completion handler with data, response, and error."
9163
+ ],
9164
+ explanation: "This is the fundamental sequence for a basic data task in URLSession.",
9197
9165
  points: 10,
9198
- difficulty: "easy",
9199
- topic: "Swift Constants",
9166
+ difficulty: "medium",
9167
+ topic: "Swift Networking",
9200
9168
  verifiedCategory: category
9201
9169
  }, null, 2);
9202
9170
  return `${attemptInfo}You are an expert Question Author for advanced technical education, specializing in: ${category}.
9203
- Your mission is to create a high-quality, technically accurate Short Answer Question.
9171
+ Your mission is to create a high-quality, technically accurate Sequence Question.
9204
9172
 
9205
9173
  ## Core Rules (Non-negotiable)
9206
9174
  1. **Category Purity:** The question MUST be exclusively about **${category}**.
9207
- 2. **Objective Answer:** The question must have a short, factual, and objective answer. Avoid questions that are subjective or require long explanations.
9208
- 3. **Schema Integrity:** The response MUST be ONLY a single, valid JSON object.
9175
+ 2. **Unambiguous Order:** The items MUST represent a clear, objective process, timeline, or order with only one correct sequence.
9176
+ 3. **Schema Integrity:** The response MUST be ONLY a single, valid JSON object that strictly follows the provided schema.
9209
9177
 
9210
9178
  ## CRITICAL CONTEXT FOR THIS QUESTION
9211
9179
  ${contextStrings}
9212
9180
 
9213
9181
  ## Task: Generate the Question
9214
- Based on all the rules and context above, generate a single Short Answer Question.
9182
+ Based on all the rules and context above, generate a single Sequence Question.
9215
9183
 
9216
9184
  ### Input Parameters
9217
9185
  - **Topic for Question:** ${quizContext?.plannedTopic || "General"}
9218
9186
  - **Language for Text:** ${language3}
9219
9187
  - **Difficulty Level:** ${difficulty}
9188
+ - **Number of Items:** Generate an array 'itemsInCorrectOrder' containing exactly ${numberOfItems} items. The array itself MUST be in the correct final sequence.
9220
9189
 
9221
9190
  ### Required JSON Output Format
9222
9191
  Your response must be ONLY the JSON object, matching this exact structure:
@@ -9225,11 +9194,11 @@ ${exampleJson}
9225
9194
 
9226
9195
  Now, generate the JSON for the requested question.`;
9227
9196
  }
9228
- async function generateShortAnswerQuestion(clientInput, apiKey) {
9197
+ async function generateSequenceQuestion(clientInput, apiKey) {
9229
9198
  const ai = new genai.GoogleGenAI({ apiKey });
9230
9199
  const model = "gemini-2.5-flash";
9231
9200
  const config2 = {
9232
- temperature: 0.5,
9201
+ temperature: 0.7,
9233
9202
  responseMimeType: "application/json",
9234
9203
  thinkingConfig: {
9235
9204
  thinkingBudget: 4e3
@@ -9258,8 +9227,11 @@ async function generateShortAnswerQuestion(clientInput, apiKey) {
9258
9227
  if (!rawText) throw new Error("AI returned an empty response.");
9259
9228
  const parsedJson = JSON.parse(rawText);
9260
9229
  DebugLogger.logValidation(attempt, "JSON Parsed Successfully", parsedJson);
9261
- const aiGeneratedContent = AIShortAnswerOutputFieldsSchema.parse(parsedJson);
9230
+ const aiGeneratedContent = AISequenceOutputFieldsSchema.parse(parsedJson);
9262
9231
  DebugLogger.logValidation(attempt, "Zod Schema Validated", aiGeneratedContent);
9232
+ if (aiGeneratedContent.itemsInCorrectOrder.length !== clientInput.numberOfItems) {
9233
+ throw new Error(`AI generated ${aiGeneratedContent.itemsInCorrectOrder.length} items, but ${clientInput.numberOfItems} were required.`);
9234
+ }
9263
9235
  if (clientInput.quizContext?.originalCategory) {
9264
9236
  const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
9265
9237
  const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
@@ -9267,12 +9239,19 @@ async function generateShortAnswerQuestion(clientInput, apiKey) {
9267
9239
  throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
9268
9240
  }
9269
9241
  }
9242
+ const finalItems = [];
9243
+ const finalCorrectOrder = [];
9244
+ aiGeneratedContent.itemsInCorrectOrder.forEach((content4) => {
9245
+ const id2 = generateUniqueId("seqi_");
9246
+ finalItems.push({ id: id2, content: content4 });
9247
+ finalCorrectOrder.push(id2);
9248
+ });
9270
9249
  const completeQuestion = {
9271
- id: generateUniqueId("saq_ai_"),
9272
- questionType: "short_answer",
9250
+ id: generateUniqueId("seq_ai_"),
9251
+ questionType: "sequence",
9273
9252
  prompt: aiGeneratedContent.prompt,
9274
- acceptedAnswers: aiGeneratedContent.acceptedAnswers,
9275
- isCaseSensitive: clientInput.isCaseSensitive,
9253
+ items: finalItems,
9254
+ correctOrder: finalCorrectOrder,
9276
9255
  explanation: aiGeneratedContent.explanation,
9277
9256
  points: aiGeneratedContent.points,
9278
9257
  topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
@@ -9284,10 +9263,10 @@ async function generateShortAnswerQuestion(clientInput, apiKey) {
9284
9263
  category: clientInput.quizContext?.originalCategory,
9285
9264
  imageUrl: clientInput.imageUrl
9286
9265
  };
9287
- const validatedQuestion = ShortAnswerQuestionZodSchema.parse(completeQuestion);
9266
+ const validatedQuestion = SequenceQuestionZodSchema.parse(completeQuestion);
9288
9267
  attemptResults.push({ success: true, duration, promptLength: promptText.length, responseLength: rawText.length, promptHash });
9289
9268
  console.log(`
9290
- \u2705 Short Answer generation successful on attempt ${attempt} (${duration}ms)`);
9269
+ \u2705 Sequence generation successful on attempt ${attempt} (${duration}ms)`);
9291
9270
  if (attempt > 1) DebugLogger.logAttemptSummary(attemptResults);
9292
9271
  return { question: validatedQuestion };
9293
9272
  } catch (error) {
@@ -9303,16 +9282,22 @@ async function generateShortAnswerQuestion(clientInput, apiKey) {
9303
9282
  }
9304
9283
  }
9305
9284
  DebugLogger.logAttemptSummary(attemptResults);
9306
- const errorMessage = `Failed to generate Short Answer question after ${MAX_RETRY_ATTEMPTS7} attempts. Last error: ${lastError?.message}`;
9285
+ const errorMessage = `Failed to generate Sequence question after ${MAX_RETRY_ATTEMPTS7} attempts. Last error: ${lastError?.message}`;
9307
9286
  console.error("\n\u274C Final Result: FAILED");
9308
9287
  console.error(errorMessage);
9309
9288
  return { error: errorMessage };
9310
9289
  }
9311
- BaseQuestionGenerationClientInputSchema.extend({});
9312
- var AITrueFalseOutputFieldsSchema = zod.z.object({
9313
- prompt: zod.z.string().describe("The statement that the user will evaluate as true or false."),
9314
- correctAnswer: zod.z.boolean(),
9315
- explanation: zod.z.string().optional().describe("An explanation of why the statement is true or false, especially important if false."),
9290
+ BaseQuestionGenerationClientInputSchema.extend({
9291
+ numberOfPairs: zod.z.number().int().min(2).max(8).optional().default(4),
9292
+ shuffleOptions: zod.z.boolean().optional().default(true)
9293
+ });
9294
+ var AIMatchingOutputFieldsSchema = zod.z.object({
9295
+ prompt: zod.z.string().describe("The instructional text for the user, e.g., 'Match the concept to its definition.'"),
9296
+ correctPairs: zod.z.array(zod.z.object({
9297
+ promptText: zod.z.string().min(1).describe("The text for the left-hand side item (the prompt)."),
9298
+ optionText: zod.z.string().min(1).describe("The text for the right-hand side item (the matching option).")
9299
+ })).min(2),
9300
+ explanation: zod.z.string().optional(),
9316
9301
  points: zod.z.number().optional().default(10),
9317
9302
  difficulty: zod.z.enum(["easy", "medium", "hard"]).optional(),
9318
9303
  topic: zod.z.string().optional(),
@@ -9320,55 +9305,57 @@ var AITrueFalseOutputFieldsSchema = zod.z.object({
9320
9305
  // Thêm để xác thực
9321
9306
  });
9322
9307
 
9323
- // src/ai/flows/question-gen/generate-true-false-question.ts
9308
+ // src/ai/flows/question-gen/generate-matching-question.ts
9324
9309
  var MAX_RETRY_ATTEMPTS8 = 3;
9325
9310
  var RETRY_DELAY_MS8 = 3e3;
9326
9311
  function buildEnhancedPrompt8(clientInput, attemptNumber) {
9327
- const { quizContext, language: language3, difficulty, imageUrl } = clientInput;
9312
+ const { quizContext, language: language3, difficulty, numberOfPairs, imageUrl } = clientInput;
9328
9313
  const category = quizContext?.originalCategory || "the specified technical category";
9329
9314
  const attemptInfo = attemptNumber > 1 ? `
9330
9315
  ## DEBUG INFO - This is attempt #${attemptNumber}
9331
- Previous attempts failed. Ensure the JSON is valid and 'correctAnswer' is a boolean.
9316
+ Previous attempts failed. Please ensure the 'correctPairs' array has exactly the required number of items and the JSON is valid.
9332
9317
 
9333
9318
  ` : "";
9334
- const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The True/False statement must be directly related to the content of this image.` : "";
9335
- const misconceptionGuidance = quizContext?.targetMisconception ? `**Target Misconception:** The statement you create MUST be FALSE and based on this common mistake: "${quizContext.targetMisconception}"` : "";
9319
+ const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The matching pairs must be directly related to the content of this image.` : "";
9336
9320
  const contextStrings = [
9337
9321
  `**Required Category:** ${category}`,
9338
9322
  quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
9339
9323
  imageContextInstruction,
9340
9324
  quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
9341
- misconceptionGuidance,
9342
- quizContext?.difficultyReason && `**Pedagogical Reason:** ${quizContext.difficultyReason}`
9325
+ quizContext?.targetMisconception && `**Target Misconception:** Design a pair that specifically tests this confusion: "${quizContext.targetMisconception}"`
9343
9326
  ].filter(Boolean).map((s2) => `- ${s2}`).join("\n");
9344
9327
  const exampleJson = JSON.stringify({
9345
- prompt: "In Swift, you must explicitly unwrap an Optional value before you can use its stored value.",
9346
- correctAnswer: true,
9347
- explanation: "Optional values in Swift represent the presence or absence of a value. To access the value when it exists, you must unwrap it using methods like 'if let', 'guard let', or the force unwrap operator '!'.",
9328
+ prompt: "Match each Swift collection type to its primary characteristic.",
9329
+ correctPairs: [
9330
+ { "promptText": "Array", "optionText": "An ordered, random-access collection." },
9331
+ { "promptText": "Set", "optionText": "An unordered collection of unique elements." },
9332
+ { "promptText": "Dictionary", "optionText": "An unordered collection of key-value associations." }
9333
+ ],
9334
+ explanation: "These are the fundamental characteristics of Swift's main collection types.",
9348
9335
  points: 10,
9349
9336
  difficulty: "easy",
9350
- topic: "Swift Optionals",
9337
+ topic: "Swift Collection Types",
9351
9338
  verifiedCategory: category
9352
9339
  }, null, 2);
9353
9340
  return `${attemptInfo}You are an expert Question Author for advanced technical education, specializing in: ${category}.
9354
- Your mission is to create a high-quality, technically accurate True/False Question.
9341
+ Your mission is to create a high-quality, technically accurate Matching Question.
9355
9342
 
9356
9343
  ## Core Rules (Non-negotiable)
9357
- 1. **Category Purity:** The statement ('prompt') MUST be exclusively about **${category}**.
9358
- 2. **Clarity:** The statement must be definitively true or false, with no ambiguity.
9359
- 3. **Misconception Priority:** If a Target Misconception is provided, the statement MUST be FALSE and reflect that misconception. This is a critical rule.
9360
- 4. **Schema Integrity:** The response MUST be ONLY a single, valid JSON object.
9344
+ 1. **Category Purity:** The question MUST be exclusively about **${category}**.
9345
+ 2. **Logical Pairs:** The items to be matched must have a clear, one-to-one relationship.
9346
+ 3. **Schema Integrity:** The response MUST be ONLY a single, valid JSON object that strictly follows the provided schema.
9361
9347
 
9362
9348
  ## CRITICAL CONTEXT FOR THIS QUESTION
9363
9349
  ${contextStrings}
9364
9350
 
9365
9351
  ## Task: Generate the Question
9366
- Based on all the rules and context above, generate a single True/False Question.
9352
+ Based on all the rules and context above, generate a single Matching Question.
9367
9353
 
9368
9354
  ### Input Parameters
9369
9355
  - **Topic for Question:** ${quizContext?.plannedTopic || "General"}
9370
9356
  - **Language for Text:** ${language3}
9371
9357
  - **Difficulty Level:** ${difficulty}
9358
+ - **Number of Pairs:** Generate exactly ${numberOfPairs} correct pairs in the 'correctPairs' array.
9372
9359
 
9373
9360
  ### Required JSON Output Format
9374
9361
  Your response must be ONLY the JSON object, matching this exact structure:
@@ -9377,11 +9364,11 @@ ${exampleJson}
9377
9364
 
9378
9365
  Now, generate the JSON for the requested question.`;
9379
9366
  }
9380
- async function generateTrueFalseQuestion(clientInput, apiKey) {
9367
+ async function generateMatchingQuestion(clientInput, apiKey) {
9381
9368
  const ai = new genai.GoogleGenAI({ apiKey });
9382
9369
  const model = "gemini-2.5-flash";
9383
9370
  const config2 = {
9384
- temperature: 0.6,
9371
+ temperature: 0.8,
9385
9372
  responseMimeType: "application/json",
9386
9373
  thinkingConfig: {
9387
9374
  thinkingBudget: 4e3
@@ -9410,10 +9397,10 @@ async function generateTrueFalseQuestion(clientInput, apiKey) {
9410
9397
  if (!rawText) throw new Error("AI returned an empty response.");
9411
9398
  const parsedJson = JSON.parse(rawText);
9412
9399
  DebugLogger.logValidation(attempt, "JSON Parsed Successfully", parsedJson);
9413
- const aiGeneratedContent = AITrueFalseOutputFieldsSchema.parse(parsedJson);
9400
+ const aiGeneratedContent = AIMatchingOutputFieldsSchema.parse(parsedJson);
9414
9401
  DebugLogger.logValidation(attempt, "Zod Schema Validated", aiGeneratedContent);
9415
- if (clientInput.quizContext?.targetMisconception && aiGeneratedContent.correctAnswer === true) {
9416
- throw new Error("AI failed to follow the Misconception Priority rule. The answer should have been false.");
9402
+ if (aiGeneratedContent.correctPairs.length !== clientInput.numberOfPairs) {
9403
+ throw new Error(`AI generated ${aiGeneratedContent.correctPairs.length} pairs, but ${clientInput.numberOfPairs} were required.`);
9417
9404
  }
9418
9405
  if (clientInput.quizContext?.originalCategory) {
9419
9406
  const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
@@ -9422,11 +9409,24 @@ async function generateTrueFalseQuestion(clientInput, apiKey) {
9422
9409
  throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
9423
9410
  }
9424
9411
  }
9412
+ const finalPrompts = [];
9413
+ const finalOptions = [];
9414
+ const finalCorrectAnswerMap = [];
9415
+ aiGeneratedContent.correctPairs.forEach((pair2) => {
9416
+ const promptId = generateUniqueId("m_p_");
9417
+ const optionId = generateUniqueId("m_o_");
9418
+ finalPrompts.push({ id: promptId, content: pair2.promptText });
9419
+ finalOptions.push({ id: optionId, content: pair2.optionText });
9420
+ finalCorrectAnswerMap.push({ promptId, optionId });
9421
+ });
9425
9422
  const completeQuestion = {
9426
- id: generateUniqueId("tf_ai_"),
9427
- questionType: "true_false",
9423
+ id: generateUniqueId("match_ai_"),
9424
+ questionType: "matching",
9428
9425
  prompt: aiGeneratedContent.prompt,
9429
- correctAnswer: aiGeneratedContent.correctAnswer,
9426
+ prompts: finalPrompts,
9427
+ options: finalOptions,
9428
+ correctAnswerMap: finalCorrectAnswerMap,
9429
+ shuffleOptions: clientInput.shuffleOptions,
9430
9430
  explanation: aiGeneratedContent.explanation,
9431
9431
  points: aiGeneratedContent.points,
9432
9432
  topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
@@ -9438,10 +9438,10 @@ async function generateTrueFalseQuestion(clientInput, apiKey) {
9438
9438
  category: clientInput.quizContext?.originalCategory,
9439
9439
  imageUrl: clientInput.imageUrl
9440
9440
  };
9441
- const validatedQuestion = TrueFalseQuestionZodSchema.parse(completeQuestion);
9441
+ const validatedQuestion = MatchingQuestionZodSchema.parse(completeQuestion);
9442
9442
  attemptResults.push({ success: true, duration, promptLength: promptText.length, responseLength: rawText.length, promptHash });
9443
9443
  console.log(`
9444
- \u2705 True/False generation successful on attempt ${attempt} (${duration}ms)`);
9444
+ \u2705 Matching generation successful on attempt ${attempt} (${duration}ms)`);
9445
9445
  if (attempt > 1) DebugLogger.logAttemptSummary(attemptResults);
9446
9446
  return { question: validatedQuestion };
9447
9447
  } catch (error) {
@@ -9457,7 +9457,167 @@ async function generateTrueFalseQuestion(clientInput, apiKey) {
9457
9457
  }
9458
9458
  }
9459
9459
  DebugLogger.logAttemptSummary(attemptResults);
9460
- const errorMessage = `Failed to generate True/False question after ${MAX_RETRY_ATTEMPTS8} attempts. Last error: ${lastError?.message}`;
9460
+ const errorMessage = `Failed to generate Matching question after ${MAX_RETRY_ATTEMPTS8} attempts. Last error: ${lastError?.message}`;
9461
+ console.error("\n\u274C Final Result: FAILED");
9462
+ console.error(errorMessage);
9463
+ return { error: errorMessage };
9464
+ }
9465
+ BaseQuestionGenerationClientInputSchema.extend({
9466
+ codingLanguage: zod.z.enum(["cpp", "javascript", "python", "swift", "csharp"])
9467
+ });
9468
+ var AICodingQuestionOutputSchema = zod.z.object({
9469
+ prompt: zod.z.string().describe("The problem description for the user."),
9470
+ functionSignature: zod.z.string().optional().describe("A suggested function signature for the user to implement."),
9471
+ solutionCode: zod.z.string().describe("A complete, correct model solution in the specified language."),
9472
+ testCases: zod.z.array(zod.z.object({
9473
+ input: zod.z.array(zod.z.any()),
9474
+ // FIX: Use .refine to make it explicitly non-optional for TypeScript's inference
9475
+ expectedOutput: zod.z.any().refine((val) => val !== void 0, {
9476
+ message: "expectedOutput is required and cannot be undefined."
9477
+ }),
9478
+ isPublic: zod.z.boolean()
9479
+ })).min(3, { message: "Must provide at least 3 test cases." }),
9480
+ verifiedCodingLanguage: zod.z.enum(["cpp", "javascript", "python", "swift", "csharp"]).optional().describe("The programming language this question actually addresses.")
9481
+ });
9482
+
9483
+ // src/ai/flows/question-gen/generate-coding-question.ts
9484
+ var MAX_RETRY_ATTEMPTS9 = 3;
9485
+ var RETRY_DELAY_MS9 = 3e3;
9486
+ function buildEnhancedPrompt9(clientInput, attemptNumber) {
9487
+ const { quizContext, difficulty, codingLanguage, language: language3, imageUrl } = clientInput;
9488
+ const subject = quizContext?.originalSubject || codingLanguage;
9489
+ const attemptInfo = attemptNumber > 1 ? `
9490
+ ## DEBUG INFO - This is attempt #${attemptNumber}
9491
+ Previous attempts failed. Pay strict attention to the JSON schema and all rules. Ensure every object in the 'testCases' array has a non-null 'expectedOutput' field.
9492
+
9493
+ ` : "";
9494
+ const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The coding problem must be directly related to processing or interpreting the content of this image.` : "";
9495
+ const contextStrings = [
9496
+ `**Subject:** ${subject}`,
9497
+ quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
9498
+ imageContextInstruction,
9499
+ quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
9500
+ quizContext?.targetMisconception && `**Target Misconception:** The problem should test against this common error: "${quizContext.targetMisconception}"`
9501
+ ].filter(Boolean).map((s2) => `- ${s2}`).join("\n");
9502
+ const exampleJson = JSON.stringify({
9503
+ prompt: "Write a function named 'add' that takes two integers and returns their sum.",
9504
+ functionSignature: "function add(a, b) { ... }",
9505
+ solutionCode: "function add(a, b) {\n return a + b;\n}",
9506
+ testCases: [
9507
+ { "input": [1, 2], "expectedOutput": 3, "isPublic": true },
9508
+ { "input": [-1, 1], "expectedOutput": 0, "isPublic": true },
9509
+ { "input": [0, 0], "expectedOutput": 0, "isPublic": false }
9510
+ ],
9511
+ verifiedCodingLanguage: "javascript"
9512
+ }, null, 2);
9513
+ return `${attemptInfo}You are an expert programming problem designer for ${subject}.
9514
+ Generate a single, high-quality Coding question.
9515
+
9516
+ ## Core Rules
9517
+ 1. **Language Purity:** All code ('functionSignature', 'solutionCode') MUST be in **${codingLanguage}**.
9518
+ 2. **Context Adherence:** The problem MUST be directly related to the provided context.
9519
+ 3. **Format Integrity:** You MUST return ONLY a single, valid JSON object.
9520
+ 4. **Test Case Integrity:** Every test case object in the 'testCases' array MUST have a non-null and defined 'expectedOutput' field. This is a critical rule.
9521
+
9522
+ ## CRITICAL CONTEXT FOR THIS QUESTION
9523
+ ${contextStrings}
9524
+
9525
+ ## Task: Generate the Question
9526
+ ### Input Parameters
9527
+ - **Topic for Question:** ${quizContext?.plannedTopic || "General"}
9528
+ - **Natural Language for Text:** ${language3}
9529
+ - **Coding Language:** ${codingLanguage}
9530
+ - **Difficulty Level:** ${difficulty}
9531
+
9532
+ ### Required JSON Output Format
9533
+ Your response must be ONLY the JSON object, matching this exact structure:
9534
+
9535
+ ${exampleJson}
9536
+
9537
+ Now, generate the JSON for the requested question.`;
9538
+ }
9539
+ async function generateCodingQuestion(clientInput, apiKey) {
9540
+ const ai = new genai.GoogleGenAI({ apiKey });
9541
+ const model = "gemini-2.5-flash";
9542
+ const config2 = {
9543
+ temperature: 0.5,
9544
+ responseMimeType: "application/json",
9545
+ thinkingConfig: {
9546
+ thinkingBudget: 5e3
9547
+ }
9548
+ };
9549
+ const attemptResults = [];
9550
+ let lastError = null;
9551
+ for (let attempt = 1; attempt <= MAX_RETRY_ATTEMPTS9; attempt++) {
9552
+ const startTime = Date.now();
9553
+ const promptText = buildEnhancedPrompt9(clientInput, attempt);
9554
+ const promptHash = Buffer.from(promptText).toString("base64").slice(0, 10);
9555
+ try {
9556
+ DebugLogger.logPrompt(attempt, promptText, { ...clientInput, attemptNumber: attempt, promptHash });
9557
+ const parts = [{ text: promptText }];
9558
+ if (clientInput.imageUrl) {
9559
+ const mimeType = clientInput.imageUrl.endsWith(".png") ? "image/png" : "image/jpeg";
9560
+ const imagePart = await urlToGenerativePart(clientInput.imageUrl, mimeType);
9561
+ parts.unshift(imagePart);
9562
+ }
9563
+ const contents = [{ role: "user", parts }];
9564
+ const aiResult = await ai.models.generateContent({ model, config: config2, contents });
9565
+ const response = aiResult;
9566
+ const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
9567
+ const duration = Date.now() - startTime;
9568
+ DebugLogger.logResponse(attempt, rawText);
9569
+ if (!rawText) throw new Error("AI returned an empty response.");
9570
+ const parsedJson = JSON.parse(rawText);
9571
+ DebugLogger.logValidation(attempt, "JSON Parsed Successfully", parsedJson);
9572
+ const aiGeneratedContent = AICodingQuestionOutputSchema.parse(parsedJson);
9573
+ DebugLogger.logValidation(attempt, "Zod Schema Validated", aiGeneratedContent);
9574
+ if (aiGeneratedContent.verifiedCodingLanguage && aiGeneratedContent.verifiedCodingLanguage !== clientInput.codingLanguage) {
9575
+ throw new Error(`Language mismatch: Required ${clientInput.codingLanguage}, but AI generated for ${aiGeneratedContent.verifiedCodingLanguage}.`);
9576
+ }
9577
+ const testCases = aiGeneratedContent.testCases.map((tc) => ({
9578
+ id: generateUniqueId("tc_"),
9579
+ input: tc.input,
9580
+ expectedOutput: tc.expectedOutput,
9581
+ isPublic: tc.isPublic
9582
+ }));
9583
+ const completeQuestion = {
9584
+ id: generateUniqueId("coding_"),
9585
+ questionType: "coding",
9586
+ prompt: aiGeneratedContent.prompt,
9587
+ codingLanguage: clientInput.codingLanguage,
9588
+ functionSignature: aiGeneratedContent.functionSignature,
9589
+ solutionCode: aiGeneratedContent.solutionCode,
9590
+ testCases,
9591
+ points: 25,
9592
+ topic: clientInput.quizContext?.originalTopic,
9593
+ difficulty: clientInput.difficulty,
9594
+ contextCode: clientInput.quizContext?.plannedContextId,
9595
+ bloomLevel: clientInput.quizContext?.plannedBloomLevel,
9596
+ learningObjective: clientInput.quizContext?.originalLoId,
9597
+ subject: clientInput.quizContext?.originalSubject,
9598
+ category: clientInput.quizContext?.originalCategory,
9599
+ imageUrl: clientInput.imageUrl
9600
+ };
9601
+ CodingQuestionZodSchema.parse(completeQuestion);
9602
+ attemptResults.push({ success: true, duration, promptLength: promptText.length, responseLength: rawText.length, promptHash });
9603
+ console.log(`
9604
+ \u2705 Coding question generation successful on attempt ${attempt} (${duration}ms)`);
9605
+ if (attempt > 1) DebugLogger.logAttemptSummary(attemptResults);
9606
+ return { question: completeQuestion };
9607
+ } catch (error) {
9608
+ lastError = error;
9609
+ const duration = Date.now() - startTime;
9610
+ attemptResults.push({ success: false, duration, error: error.message, promptLength: promptText.length, promptHash });
9611
+ const willRetry = attempt < MAX_RETRY_ATTEMPTS9;
9612
+ DebugLogger.logRetryInfo(attempt, error, willRetry);
9613
+ if (willRetry) {
9614
+ console.log(`\u23F3 Retrying in ${RETRY_DELAY_MS9}ms...`);
9615
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS9));
9616
+ }
9617
+ }
9618
+ }
9619
+ DebugLogger.logAttemptSummary(attemptResults);
9620
+ const errorMessage = `Failed to generate Coding question after ${MAX_RETRY_ATTEMPTS9} attempts. Last error: ${lastError?.message}`;
9461
9621
  console.error("\n\u274C Final Result: FAILED");
9462
9622
  console.error(errorMessage);
9463
9623
  return { error: errorMessage };
@@ -10107,166 +10267,6 @@ TopicDataService.EXPECTED_HEADERS = [
10107
10267
  "STEM Element(s)",
10108
10268
  "Bloom\u2019s Level(s) Guideline"
10109
10269
  ];
10110
- BaseQuestionGenerationClientInputSchema.extend({
10111
- codingLanguage: zod.z.enum(["cpp", "javascript", "python", "swift", "csharp"])
10112
- });
10113
- var AICodingQuestionOutputSchema = zod.z.object({
10114
- prompt: zod.z.string().describe("The problem description for the user."),
10115
- functionSignature: zod.z.string().optional().describe("A suggested function signature for the user to implement."),
10116
- solutionCode: zod.z.string().describe("A complete, correct model solution in the specified language."),
10117
- testCases: zod.z.array(zod.z.object({
10118
- input: zod.z.array(zod.z.any()),
10119
- // FIX: Use .refine to make it explicitly non-optional for TypeScript's inference
10120
- expectedOutput: zod.z.any().refine((val) => val !== void 0, {
10121
- message: "expectedOutput is required and cannot be undefined."
10122
- }),
10123
- isPublic: zod.z.boolean()
10124
- })).min(3, { message: "Must provide at least 3 test cases." }),
10125
- verifiedCodingLanguage: zod.z.enum(["cpp", "javascript", "python", "swift", "csharp"]).optional().describe("The programming language this question actually addresses.")
10126
- });
10127
-
10128
- // src/ai/flows/question-gen/generate-coding-question.ts
10129
- var MAX_RETRY_ATTEMPTS9 = 3;
10130
- var RETRY_DELAY_MS9 = 3e3;
10131
- function buildEnhancedPrompt9(clientInput, attemptNumber) {
10132
- const { quizContext, difficulty, codingLanguage, language: language3, imageUrl } = clientInput;
10133
- const subject = quizContext?.originalSubject || codingLanguage;
10134
- const attemptInfo = attemptNumber > 1 ? `
10135
- ## DEBUG INFO - This is attempt #${attemptNumber}
10136
- Previous attempts failed. Pay strict attention to the JSON schema and all rules. Ensure every object in the 'testCases' array has a non-null 'expectedOutput' field.
10137
-
10138
- ` : "";
10139
- const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The coding problem must be directly related to processing or interpreting the content of this image.` : "";
10140
- const contextStrings = [
10141
- `**Subject:** ${subject}`,
10142
- quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
10143
- imageContextInstruction,
10144
- quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
10145
- quizContext?.targetMisconception && `**Target Misconception:** The problem should test against this common error: "${quizContext.targetMisconception}"`
10146
- ].filter(Boolean).map((s2) => `- ${s2}`).join("\n");
10147
- const exampleJson = JSON.stringify({
10148
- prompt: "Write a function named 'add' that takes two integers and returns their sum.",
10149
- functionSignature: "function add(a, b) { ... }",
10150
- solutionCode: "function add(a, b) {\n return a + b;\n}",
10151
- testCases: [
10152
- { "input": [1, 2], "expectedOutput": 3, "isPublic": true },
10153
- { "input": [-1, 1], "expectedOutput": 0, "isPublic": true },
10154
- { "input": [0, 0], "expectedOutput": 0, "isPublic": false }
10155
- ],
10156
- verifiedCodingLanguage: "javascript"
10157
- }, null, 2);
10158
- return `${attemptInfo}You are an expert programming problem designer for ${subject}.
10159
- Generate a single, high-quality Coding question.
10160
-
10161
- ## Core Rules
10162
- 1. **Language Purity:** All code ('functionSignature', 'solutionCode') MUST be in **${codingLanguage}**.
10163
- 2. **Context Adherence:** The problem MUST be directly related to the provided context.
10164
- 3. **Format Integrity:** You MUST return ONLY a single, valid JSON object.
10165
- 4. **Test Case Integrity:** Every test case object in the 'testCases' array MUST have a non-null and defined 'expectedOutput' field. This is a critical rule.
10166
-
10167
- ## CRITICAL CONTEXT FOR THIS QUESTION
10168
- ${contextStrings}
10169
-
10170
- ## Task: Generate the Question
10171
- ### Input Parameters
10172
- - **Topic for Question:** ${quizContext?.plannedTopic || "General"}
10173
- - **Natural Language for Text:** ${language3}
10174
- - **Coding Language:** ${codingLanguage}
10175
- - **Difficulty Level:** ${difficulty}
10176
-
10177
- ### Required JSON Output Format
10178
- Your response must be ONLY the JSON object, matching this exact structure:
10179
-
10180
- ${exampleJson}
10181
-
10182
- Now, generate the JSON for the requested question.`;
10183
- }
10184
- async function generateCodingQuestion(clientInput, apiKey) {
10185
- const ai = new genai.GoogleGenAI({ apiKey });
10186
- const model = "gemini-2.5-flash";
10187
- const config2 = {
10188
- temperature: 0.5,
10189
- responseMimeType: "application/json",
10190
- thinkingConfig: {
10191
- thinkingBudget: 5e3
10192
- }
10193
- };
10194
- const attemptResults = [];
10195
- let lastError = null;
10196
- for (let attempt = 1; attempt <= MAX_RETRY_ATTEMPTS9; attempt++) {
10197
- const startTime = Date.now();
10198
- const promptText = buildEnhancedPrompt9(clientInput, attempt);
10199
- const promptHash = Buffer.from(promptText).toString("base64").slice(0, 10);
10200
- try {
10201
- DebugLogger.logPrompt(attempt, promptText, { ...clientInput, attemptNumber: attempt, promptHash });
10202
- const parts = [{ text: promptText }];
10203
- if (clientInput.imageUrl) {
10204
- const mimeType = clientInput.imageUrl.endsWith(".png") ? "image/png" : "image/jpeg";
10205
- const imagePart = await urlToGenerativePart(clientInput.imageUrl, mimeType);
10206
- parts.unshift(imagePart);
10207
- }
10208
- const contents = [{ role: "user", parts }];
10209
- const aiResult = await ai.models.generateContent({ model, config: config2, contents });
10210
- const response = aiResult;
10211
- const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
10212
- const duration = Date.now() - startTime;
10213
- DebugLogger.logResponse(attempt, rawText);
10214
- if (!rawText) throw new Error("AI returned an empty response.");
10215
- const parsedJson = JSON.parse(rawText);
10216
- DebugLogger.logValidation(attempt, "JSON Parsed Successfully", parsedJson);
10217
- const aiGeneratedContent = AICodingQuestionOutputSchema.parse(parsedJson);
10218
- DebugLogger.logValidation(attempt, "Zod Schema Validated", aiGeneratedContent);
10219
- if (aiGeneratedContent.verifiedCodingLanguage && aiGeneratedContent.verifiedCodingLanguage !== clientInput.codingLanguage) {
10220
- throw new Error(`Language mismatch: Required ${clientInput.codingLanguage}, but AI generated for ${aiGeneratedContent.verifiedCodingLanguage}.`);
10221
- }
10222
- const testCases = aiGeneratedContent.testCases.map((tc) => ({
10223
- id: generateUniqueId("tc_"),
10224
- input: tc.input,
10225
- expectedOutput: tc.expectedOutput,
10226
- isPublic: tc.isPublic
10227
- }));
10228
- const completeQuestion = {
10229
- id: generateUniqueId("coding_"),
10230
- questionType: "coding",
10231
- prompt: aiGeneratedContent.prompt,
10232
- codingLanguage: clientInput.codingLanguage,
10233
- functionSignature: aiGeneratedContent.functionSignature,
10234
- solutionCode: aiGeneratedContent.solutionCode,
10235
- testCases,
10236
- points: 25,
10237
- topic: clientInput.quizContext?.originalTopic,
10238
- difficulty: clientInput.difficulty,
10239
- contextCode: clientInput.quizContext?.plannedContextId,
10240
- bloomLevel: clientInput.quizContext?.plannedBloomLevel,
10241
- learningObjective: clientInput.quizContext?.originalLoId,
10242
- subject: clientInput.quizContext?.originalSubject,
10243
- category: clientInput.quizContext?.originalCategory,
10244
- imageUrl: clientInput.imageUrl
10245
- };
10246
- CodingQuestionZodSchema.parse(completeQuestion);
10247
- attemptResults.push({ success: true, duration, promptLength: promptText.length, responseLength: rawText.length, promptHash });
10248
- console.log(`
10249
- \u2705 Coding question generation successful on attempt ${attempt} (${duration}ms)`);
10250
- if (attempt > 1) DebugLogger.logAttemptSummary(attemptResults);
10251
- return { question: completeQuestion };
10252
- } catch (error) {
10253
- lastError = error;
10254
- const duration = Date.now() - startTime;
10255
- attemptResults.push({ success: false, duration, error: error.message, promptLength: promptText.length, promptHash });
10256
- const willRetry = attempt < MAX_RETRY_ATTEMPTS9;
10257
- DebugLogger.logRetryInfo(attempt, error, willRetry);
10258
- if (willRetry) {
10259
- console.log(`\u23F3 Retrying in ${RETRY_DELAY_MS9}ms...`);
10260
- await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS9));
10261
- }
10262
- }
10263
- }
10264
- DebugLogger.logAttemptSummary(attemptResults);
10265
- const errorMessage = `Failed to generate Coding question after ${MAX_RETRY_ATTEMPTS9} attempts. Last error: ${lastError?.message}`;
10266
- console.error("\n\u274C Final Result: FAILED");
10267
- console.error(errorMessage);
10268
- return { error: errorMessage };
10269
- }
10270
10270
 
10271
10271
  // src/ai/flows/generate-questions-from-quiz-plan.ts
10272
10272
  var MAX_ATTEMPTS = 3;
@@ -105746,6 +105746,7 @@ exports.assessAndMapDocument = assessAndMapDocument;
105746
105746
  exports.cn = cn;
105747
105747
  exports.emptyQuiz = emptyQuiz;
105748
105748
  exports.exportQuizAsSCORMZip = exportQuizAsSCORMZip;
105749
+ exports.generateCodingQuestion = generateCodingQuestion;
105749
105750
  exports.generateFillInTheBlanksQuestion = generateFillInTheBlanksQuestion;
105750
105751
  exports.generateLauncherHTML = generateLauncherHTML;
105751
105752
  exports.generateLearningAnalysis = generateLearningAnalysis;