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