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