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