@thanh01.pmt/interactive-quiz-kit 1.0.24 → 1.0.27
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 +195 -243
- package/dist/{ai.js → ai.mjs} +195 -243
- package/dist/authoring.cjs +103296 -10569
- package/dist/authoring.mjs +105685 -0
- package/dist/index.cjs +2609 -206
- package/dist/{index.js → index.mjs} +2608 -205
- package/dist/player.cjs +93661 -3364
- package/dist/player.mjs +94199 -0
- package/dist/react-ui.cjs +164435 -14143
- package/dist/react-ui.mjs +167596 -0
- package/package.json +69 -66
- package/dist/authoring.js +0 -12944
- package/dist/player.js +0 -3890
- package/dist/react-ui.js +0 -17283
package/dist/ai.cjs
CHANGED
|
@@ -5,37 +5,7 @@ var zod = require('zod');
|
|
|
5
5
|
var genkit = require('genkit');
|
|
6
6
|
var googleai = require('@genkit-ai/googleai');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
var __defProps = Object.defineProperties;
|
|
10
|
-
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
11
|
-
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
12
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
13
|
-
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
14
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
15
|
-
var __spreadValues = (a, b) => {
|
|
16
|
-
for (var prop in b || (b = {}))
|
|
17
|
-
if (__hasOwnProp.call(b, prop))
|
|
18
|
-
__defNormalProp(a, prop, b[prop]);
|
|
19
|
-
if (__getOwnPropSymbols)
|
|
20
|
-
for (var prop of __getOwnPropSymbols(b)) {
|
|
21
|
-
if (__propIsEnum.call(b, prop))
|
|
22
|
-
__defNormalProp(a, prop, b[prop]);
|
|
23
|
-
}
|
|
24
|
-
return a;
|
|
25
|
-
};
|
|
26
|
-
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
27
|
-
var __objRest = (source, exclude) => {
|
|
28
|
-
var target = {};
|
|
29
|
-
for (var prop in source)
|
|
30
|
-
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
31
|
-
target[prop] = source[prop];
|
|
32
|
-
if (source != null && __getOwnPropSymbols)
|
|
33
|
-
for (var prop of __getOwnPropSymbols(source)) {
|
|
34
|
-
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
35
|
-
target[prop] = source[prop];
|
|
36
|
-
}
|
|
37
|
-
return target;
|
|
38
|
-
};
|
|
8
|
+
// src/ai/flows/question-gen/generate-fitb-question.ts
|
|
39
9
|
|
|
40
10
|
// src/utils/idGenerators.ts
|
|
41
11
|
function generateUniqueId(prefix = "id_") {
|
|
@@ -254,7 +224,7 @@ var MAX_RETRY_ATTEMPTS = 3;
|
|
|
254
224
|
var RETRY_DELAY_MS = 3e3;
|
|
255
225
|
function buildEnhancedPrompt(clientInput, attemptNumber) {
|
|
256
226
|
const { quizContext, language, difficulty, numberOfBlanks, imageUrl } = clientInput;
|
|
257
|
-
const category =
|
|
227
|
+
const category = quizContext?.originalCategory || "the specified technical category";
|
|
258
228
|
const attemptInfo = attemptNumber > 1 ? `
|
|
259
229
|
## DEBUG INFO - This is attempt #${attemptNumber}
|
|
260
230
|
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'.
|
|
@@ -263,10 +233,10 @@ Previous attempts failed. Pay strict attention to the JSON schema, especially th
|
|
|
263
233
|
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.` : "";
|
|
264
234
|
const contextStrings = [
|
|
265
235
|
`**Required Category:** ${category}`,
|
|
266
|
-
|
|
236
|
+
quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
|
|
267
237
|
imageContextInstruction,
|
|
268
|
-
|
|
269
|
-
|
|
238
|
+
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
239
|
+
quizContext?.targetMisconception && `**Target Misconception:** Design the blank to test this specific point: "${quizContext.targetMisconception}"`
|
|
270
240
|
].filter(Boolean).map((s) => `- ${s}`).join("\n");
|
|
271
241
|
const exampleJson = JSON.stringify({
|
|
272
242
|
prompt: "Complete the following Swift code snippet.",
|
|
@@ -296,7 +266,7 @@ ${contextStrings}
|
|
|
296
266
|
Based on all the rules and context above, generate a single Fill-in-the-Blanks Question.
|
|
297
267
|
|
|
298
268
|
### Input Parameters
|
|
299
|
-
- **Topic for Question:** ${
|
|
269
|
+
- **Topic for Question:** ${quizContext?.plannedTopic || "General"}
|
|
300
270
|
- **Language for Text:** ${language}
|
|
301
271
|
- **Difficulty Level:** ${difficulty}
|
|
302
272
|
- **Number of Blanks:** Generate exactly ${numberOfBlanks} segment(s) with type 'blank'.
|
|
@@ -309,7 +279,6 @@ ${exampleJson}
|
|
|
309
279
|
Now, generate the JSON for the requested question.`;
|
|
310
280
|
}
|
|
311
281
|
async function generateFillInTheBlanksQuestion(clientInput, apiKey) {
|
|
312
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
313
282
|
const ai = new genai.GoogleGenAI({ apiKey });
|
|
314
283
|
const model = "gemini-2.5-flash";
|
|
315
284
|
const config = {
|
|
@@ -326,7 +295,7 @@ async function generateFillInTheBlanksQuestion(clientInput, apiKey) {
|
|
|
326
295
|
const promptText = buildEnhancedPrompt(clientInput, attempt);
|
|
327
296
|
const promptHash = Buffer.from(promptText).toString("base64").slice(0, 10);
|
|
328
297
|
try {
|
|
329
|
-
DebugLogger.logPrompt(attempt, promptText,
|
|
298
|
+
DebugLogger.logPrompt(attempt, promptText, { ...clientInput, attemptNumber: attempt, promptHash });
|
|
330
299
|
const parts = [{ text: promptText }];
|
|
331
300
|
if (clientInput.imageUrl) {
|
|
332
301
|
const mimeType = clientInput.imageUrl.endsWith(".png") ? "image/png" : "image/jpeg";
|
|
@@ -336,7 +305,7 @@ async function generateFillInTheBlanksQuestion(clientInput, apiKey) {
|
|
|
336
305
|
const contents = [{ role: "user", parts }];
|
|
337
306
|
const aiResult = await ai.models.generateContent({ model, config, contents });
|
|
338
307
|
const response = aiResult;
|
|
339
|
-
const rawText =
|
|
308
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
340
309
|
const duration = Date.now() - startTime;
|
|
341
310
|
DebugLogger.logResponse(attempt, rawText);
|
|
342
311
|
if (!rawText) throw new Error("AI returned an empty response.");
|
|
@@ -356,8 +325,8 @@ async function generateFillInTheBlanksQuestion(clientInput, apiKey) {
|
|
|
356
325
|
throw new Error(`Segment ${index} is 'text' but is missing 'content'.`);
|
|
357
326
|
}
|
|
358
327
|
});
|
|
359
|
-
if (
|
|
360
|
-
const verifiedCategory =
|
|
328
|
+
if (clientInput.quizContext?.originalCategory) {
|
|
329
|
+
const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
|
|
361
330
|
const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
|
|
362
331
|
if (verifiedCategory && verifiedCategory !== requiredCategory) {
|
|
363
332
|
throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
|
|
@@ -383,13 +352,13 @@ async function generateFillInTheBlanksQuestion(clientInput, apiKey) {
|
|
|
383
352
|
isCaseSensitive: clientInput.isCaseSensitive,
|
|
384
353
|
explanation: aiGeneratedContent.explanation,
|
|
385
354
|
points: aiGeneratedContent.points,
|
|
386
|
-
topic: aiGeneratedContent.topic ||
|
|
355
|
+
topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
|
|
387
356
|
difficulty: clientInput.difficulty,
|
|
388
|
-
contextCode:
|
|
389
|
-
bloomLevel:
|
|
390
|
-
learningObjective:
|
|
391
|
-
subject:
|
|
392
|
-
category:
|
|
357
|
+
contextCode: clientInput.quizContext?.plannedContextId,
|
|
358
|
+
bloomLevel: clientInput.quizContext?.plannedBloomLevel,
|
|
359
|
+
learningObjective: clientInput.quizContext?.originalLoId,
|
|
360
|
+
subject: clientInput.quizContext?.originalSubject,
|
|
361
|
+
category: clientInput.quizContext?.originalCategory,
|
|
393
362
|
imageUrl: clientInput.imageUrl
|
|
394
363
|
};
|
|
395
364
|
const validatedQuestion = FillInTheBlanksQuestionZodSchema.parse(completeQuestion);
|
|
@@ -411,7 +380,7 @@ async function generateFillInTheBlanksQuestion(clientInput, apiKey) {
|
|
|
411
380
|
}
|
|
412
381
|
}
|
|
413
382
|
DebugLogger.logAttemptSummary(attemptResults);
|
|
414
|
-
const errorMessage = `Failed to generate FITB question after ${MAX_RETRY_ATTEMPTS} attempts. Last error: ${lastError
|
|
383
|
+
const errorMessage = `Failed to generate FITB question after ${MAX_RETRY_ATTEMPTS} attempts. Last error: ${lastError?.message}`;
|
|
415
384
|
console.error("\n\u274C Final Result: FAILED");
|
|
416
385
|
console.error(errorMessage);
|
|
417
386
|
return { error: errorMessage };
|
|
@@ -439,7 +408,7 @@ var MAX_RETRY_ATTEMPTS2 = 3;
|
|
|
439
408
|
var RETRY_DELAY_MS2 = 3e3;
|
|
440
409
|
function buildEnhancedPrompt2(clientInput, attemptNumber) {
|
|
441
410
|
const { quizContext, language, difficulty, numberOfPairs, imageUrl } = clientInput;
|
|
442
|
-
const category =
|
|
411
|
+
const category = quizContext?.originalCategory || "the specified technical category";
|
|
443
412
|
const attemptInfo = attemptNumber > 1 ? `
|
|
444
413
|
## DEBUG INFO - This is attempt #${attemptNumber}
|
|
445
414
|
Previous attempts failed. Please ensure the 'correctPairs' array has exactly the required number of items and the JSON is valid.
|
|
@@ -448,10 +417,10 @@ Previous attempts failed. Please ensure the 'correctPairs' array has exactly the
|
|
|
448
417
|
const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The matching pairs must be directly related to the content of this image.` : "";
|
|
449
418
|
const contextStrings = [
|
|
450
419
|
`**Required Category:** ${category}`,
|
|
451
|
-
|
|
420
|
+
quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
|
|
452
421
|
imageContextInstruction,
|
|
453
|
-
|
|
454
|
-
|
|
422
|
+
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
423
|
+
quizContext?.targetMisconception && `**Target Misconception:** Design a pair that specifically tests this confusion: "${quizContext.targetMisconception}"`
|
|
455
424
|
].filter(Boolean).map((s) => `- ${s}`).join("\n");
|
|
456
425
|
const exampleJson = JSON.stringify({
|
|
457
426
|
prompt: "Match each Swift collection type to its primary characteristic.",
|
|
@@ -481,7 +450,7 @@ ${contextStrings}
|
|
|
481
450
|
Based on all the rules and context above, generate a single Matching Question.
|
|
482
451
|
|
|
483
452
|
### Input Parameters
|
|
484
|
-
- **Topic for Question:** ${
|
|
453
|
+
- **Topic for Question:** ${quizContext?.plannedTopic || "General"}
|
|
485
454
|
- **Language for Text:** ${language}
|
|
486
455
|
- **Difficulty Level:** ${difficulty}
|
|
487
456
|
- **Number of Pairs:** Generate exactly ${numberOfPairs} correct pairs in the 'correctPairs' array.
|
|
@@ -494,7 +463,6 @@ ${exampleJson}
|
|
|
494
463
|
Now, generate the JSON for the requested question.`;
|
|
495
464
|
}
|
|
496
465
|
async function generateMatchingQuestion(clientInput, apiKey) {
|
|
497
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
498
466
|
const ai = new genai.GoogleGenAI({ apiKey });
|
|
499
467
|
const model = "gemini-2.5-flash";
|
|
500
468
|
const config = {
|
|
@@ -511,7 +479,7 @@ async function generateMatchingQuestion(clientInput, apiKey) {
|
|
|
511
479
|
const promptText = buildEnhancedPrompt2(clientInput, attempt);
|
|
512
480
|
const promptHash = Buffer.from(promptText).toString("base64").slice(0, 10);
|
|
513
481
|
try {
|
|
514
|
-
DebugLogger.logPrompt(attempt, promptText,
|
|
482
|
+
DebugLogger.logPrompt(attempt, promptText, { ...clientInput, attemptNumber: attempt, promptHash });
|
|
515
483
|
const parts = [{ text: promptText }];
|
|
516
484
|
if (clientInput.imageUrl) {
|
|
517
485
|
const mimeType = clientInput.imageUrl.endsWith(".png") ? "image/png" : "image/jpeg";
|
|
@@ -521,7 +489,7 @@ async function generateMatchingQuestion(clientInput, apiKey) {
|
|
|
521
489
|
const contents = [{ role: "user", parts }];
|
|
522
490
|
const aiResult = await ai.models.generateContent({ model, config, contents });
|
|
523
491
|
const response = aiResult;
|
|
524
|
-
const rawText =
|
|
492
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
525
493
|
const duration = Date.now() - startTime;
|
|
526
494
|
DebugLogger.logResponse(attempt, rawText);
|
|
527
495
|
if (!rawText) throw new Error("AI returned an empty response.");
|
|
@@ -532,8 +500,8 @@ async function generateMatchingQuestion(clientInput, apiKey) {
|
|
|
532
500
|
if (aiGeneratedContent.correctPairs.length !== clientInput.numberOfPairs) {
|
|
533
501
|
throw new Error(`AI generated ${aiGeneratedContent.correctPairs.length} pairs, but ${clientInput.numberOfPairs} were required.`);
|
|
534
502
|
}
|
|
535
|
-
if (
|
|
536
|
-
const verifiedCategory =
|
|
503
|
+
if (clientInput.quizContext?.originalCategory) {
|
|
504
|
+
const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
|
|
537
505
|
const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
|
|
538
506
|
if (verifiedCategory && verifiedCategory !== requiredCategory) {
|
|
539
507
|
throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
|
|
@@ -559,13 +527,13 @@ async function generateMatchingQuestion(clientInput, apiKey) {
|
|
|
559
527
|
shuffleOptions: clientInput.shuffleOptions,
|
|
560
528
|
explanation: aiGeneratedContent.explanation,
|
|
561
529
|
points: aiGeneratedContent.points,
|
|
562
|
-
topic: aiGeneratedContent.topic ||
|
|
530
|
+
topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
|
|
563
531
|
difficulty: clientInput.difficulty,
|
|
564
|
-
contextCode:
|
|
565
|
-
bloomLevel:
|
|
566
|
-
learningObjective:
|
|
567
|
-
subject:
|
|
568
|
-
category:
|
|
532
|
+
contextCode: clientInput.quizContext?.plannedContextId,
|
|
533
|
+
bloomLevel: clientInput.quizContext?.plannedBloomLevel,
|
|
534
|
+
learningObjective: clientInput.quizContext?.originalLoId,
|
|
535
|
+
subject: clientInput.quizContext?.originalSubject,
|
|
536
|
+
category: clientInput.quizContext?.originalCategory,
|
|
569
537
|
imageUrl: clientInput.imageUrl
|
|
570
538
|
};
|
|
571
539
|
const validatedQuestion = MatchingQuestionZodSchema.parse(completeQuestion);
|
|
@@ -587,7 +555,7 @@ async function generateMatchingQuestion(clientInput, apiKey) {
|
|
|
587
555
|
}
|
|
588
556
|
}
|
|
589
557
|
DebugLogger.logAttemptSummary(attemptResults);
|
|
590
|
-
const errorMessage = `Failed to generate Matching question after ${MAX_RETRY_ATTEMPTS2} attempts. Last error: ${lastError
|
|
558
|
+
const errorMessage = `Failed to generate Matching question after ${MAX_RETRY_ATTEMPTS2} attempts. Last error: ${lastError?.message}`;
|
|
591
559
|
console.error("\n\u274C Final Result: FAILED");
|
|
592
560
|
console.error(errorMessage);
|
|
593
561
|
return { error: errorMessage };
|
|
@@ -616,7 +584,7 @@ var MAX_RETRY_ATTEMPTS3 = 3;
|
|
|
616
584
|
var RETRY_DELAY_MS3 = 3e3;
|
|
617
585
|
function buildEnhancedPrompt3(clientInput, attemptNumber) {
|
|
618
586
|
const { quizContext, language, difficulty, numberOfOptions, imageUrl } = clientInput;
|
|
619
|
-
const category =
|
|
587
|
+
const category = quizContext?.originalCategory || "the specified technical category";
|
|
620
588
|
const attemptInfo = attemptNumber > 1 ? `
|
|
621
589
|
## DEBUG INFO - This is attempt #${attemptNumber}
|
|
622
590
|
Previous attempts failed...
|
|
@@ -625,11 +593,11 @@ Previous attempts failed...
|
|
|
625
593
|
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.` : "";
|
|
626
594
|
const contextStrings = [
|
|
627
595
|
`**Required Category:** ${category}`,
|
|
628
|
-
|
|
596
|
+
quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
|
|
629
597
|
imageContextInstruction,
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
598
|
+
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
599
|
+
quizContext?.targetMisconception && `**Target Misconception:** Use this to create plausible incorrect answers: "${quizContext.targetMisconception}"`,
|
|
600
|
+
quizContext?.difficultyReason && `**Pedagogical Reason:** ${quizContext.difficultyReason}`
|
|
633
601
|
].filter(Boolean).map((s) => `- ${s}`).join("\n");
|
|
634
602
|
const exampleJson = JSON.stringify({
|
|
635
603
|
prompt: `In ${category}, what is the primary purpose of the 'guard' statement?`,
|
|
@@ -661,7 +629,7 @@ ${contextStrings}
|
|
|
661
629
|
Based on all the rules and context above, generate a single Multiple Choice Question.
|
|
662
630
|
|
|
663
631
|
### Input Parameters
|
|
664
|
-
- **Topic for Question:** ${
|
|
632
|
+
- **Topic for Question:** ${quizContext?.plannedTopic || "General"}
|
|
665
633
|
- **Language for Text:** ${language}
|
|
666
634
|
- **Difficulty Level:** ${difficulty}
|
|
667
635
|
- **Number of Options:** ${numberOfOptions}
|
|
@@ -674,7 +642,6 @@ ${exampleJson}
|
|
|
674
642
|
Now, generate the JSON for the requested question.`;
|
|
675
643
|
}
|
|
676
644
|
async function generateMCQQuestion(clientInput, apiKey) {
|
|
677
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
678
645
|
const ai = new genai.GoogleGenAI({ apiKey });
|
|
679
646
|
const model = "gemini-2.5-flash";
|
|
680
647
|
const config = {
|
|
@@ -691,7 +658,7 @@ async function generateMCQQuestion(clientInput, apiKey) {
|
|
|
691
658
|
const promptText = buildEnhancedPrompt3(clientInput, attempt);
|
|
692
659
|
const promptHash = Buffer.from(promptText).toString("base64").slice(0, 10);
|
|
693
660
|
try {
|
|
694
|
-
DebugLogger.logPrompt(attempt, promptText,
|
|
661
|
+
DebugLogger.logPrompt(attempt, promptText, { ...clientInput, attemptNumber: attempt, promptHash });
|
|
695
662
|
const parts = [{ text: promptText }];
|
|
696
663
|
if (clientInput.imageUrl) {
|
|
697
664
|
const mimeType = clientInput.imageUrl.endsWith(".png") ? "image/png" : "image/jpeg";
|
|
@@ -705,7 +672,7 @@ async function generateMCQQuestion(clientInput, apiKey) {
|
|
|
705
672
|
contents
|
|
706
673
|
});
|
|
707
674
|
const response = aiResult;
|
|
708
|
-
const rawText =
|
|
675
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
709
676
|
const duration = Date.now() - startTime;
|
|
710
677
|
DebugLogger.logResponse(attempt, rawText);
|
|
711
678
|
if (!rawText) {
|
|
@@ -715,8 +682,8 @@ async function generateMCQQuestion(clientInput, apiKey) {
|
|
|
715
682
|
DebugLogger.logValidation(attempt, "JSON Parsed Successfully", parsedJson);
|
|
716
683
|
const aiGeneratedContent = AIMCQOutputFieldsSchema.parse(parsedJson);
|
|
717
684
|
DebugLogger.logValidation(attempt, "Zod Schema Validated", aiGeneratedContent);
|
|
718
|
-
if (
|
|
719
|
-
const verifiedCategory =
|
|
685
|
+
if (clientInput.quizContext?.originalCategory) {
|
|
686
|
+
const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
|
|
720
687
|
const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
|
|
721
688
|
if (verifiedCategory && verifiedCategory !== requiredCategory) {
|
|
722
689
|
throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
|
|
@@ -741,13 +708,13 @@ async function generateMCQQuestion(clientInput, apiKey) {
|
|
|
741
708
|
correctAnswerId: finalCorrectAnswerId,
|
|
742
709
|
explanation: aiGeneratedContent.explanation,
|
|
743
710
|
points: aiGeneratedContent.points,
|
|
744
|
-
topic: aiGeneratedContent.topic ||
|
|
711
|
+
topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
|
|
745
712
|
difficulty: clientInput.difficulty,
|
|
746
|
-
contextCode:
|
|
747
|
-
bloomLevel:
|
|
748
|
-
learningObjective:
|
|
749
|
-
subject:
|
|
750
|
-
category:
|
|
713
|
+
contextCode: clientInput.quizContext?.plannedContextId,
|
|
714
|
+
bloomLevel: clientInput.quizContext?.plannedBloomLevel,
|
|
715
|
+
learningObjective: clientInput.quizContext?.originalLoId,
|
|
716
|
+
subject: clientInput.quizContext?.originalSubject,
|
|
717
|
+
category: clientInput.quizContext?.originalCategory,
|
|
751
718
|
imageUrl: clientInput.imageUrl
|
|
752
719
|
};
|
|
753
720
|
const validatedQuestion = MultipleChoiceQuestionZodSchema.parse(completeQuestion);
|
|
@@ -769,7 +736,7 @@ async function generateMCQQuestion(clientInput, apiKey) {
|
|
|
769
736
|
}
|
|
770
737
|
}
|
|
771
738
|
DebugLogger.logAttemptSummary(attemptResults);
|
|
772
|
-
const errorMessage = `Failed to generate MCQ question after ${MAX_RETRY_ATTEMPTS3} attempts. Last error: ${lastError
|
|
739
|
+
const errorMessage = `Failed to generate MCQ question after ${MAX_RETRY_ATTEMPTS3} attempts. Last error: ${lastError?.message}`;
|
|
773
740
|
console.error("\n\u274C Final Result: FAILED");
|
|
774
741
|
console.error(errorMessage);
|
|
775
742
|
return { error: errorMessage };
|
|
@@ -795,7 +762,7 @@ var MAX_RETRY_ATTEMPTS4 = 3;
|
|
|
795
762
|
var RETRY_DELAY_MS4 = 3e3;
|
|
796
763
|
function buildEnhancedPrompt4(clientInput, attemptNumber) {
|
|
797
764
|
const { quizContext, language, difficulty, numberOfOptions, minCorrectAnswers, maxCorrectAnswers, imageUrl } = clientInput;
|
|
798
|
-
const category =
|
|
765
|
+
const category = quizContext?.originalCategory || "the specified technical category";
|
|
799
766
|
const attemptInfo = attemptNumber > 1 ? `
|
|
800
767
|
## DEBUG INFO - This is attempt #${attemptNumber}
|
|
801
768
|
Previous attempts failed due to validation errors. Pay close attention to the number of correct answers and the JSON schema.
|
|
@@ -804,11 +771,11 @@ Previous attempts failed due to validation errors. Pay close attention to the nu
|
|
|
804
771
|
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.` : "";
|
|
805
772
|
const contextStrings = [
|
|
806
773
|
`**Required Category:** ${category} (This is the ONLY language to be used)`,
|
|
807
|
-
|
|
774
|
+
quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
|
|
808
775
|
imageContextInstruction,
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
776
|
+
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
777
|
+
quizContext?.targetMisconception && `**Target Misconception:** Use this to create plausible incorrect answers (distractors). The misconception is: "${quizContext.targetMisconception}"`,
|
|
778
|
+
quizContext?.difficultyReason && `**Pedagogical Reason:** ${quizContext.difficultyReason}`
|
|
812
779
|
].filter(Boolean).map((s) => `- ${s}`).join("\n");
|
|
813
780
|
const exampleJson = JSON.stringify({
|
|
814
781
|
prompt: "Which of the following are considered programming paradigms?",
|
|
@@ -841,7 +808,7 @@ ${contextStrings}
|
|
|
841
808
|
Based on all the rules and context above, generate a single Multiple Response Question.
|
|
842
809
|
|
|
843
810
|
### Input Parameters
|
|
844
|
-
- **Topic for Question:** ${
|
|
811
|
+
- **Topic for Question:** ${quizContext?.plannedTopic || "General"}
|
|
845
812
|
- **Language for Text:** ${language}
|
|
846
813
|
- **Difficulty Level:** ${difficulty}
|
|
847
814
|
- **Number of Options:** Generate exactly ${numberOfOptions} options.
|
|
@@ -855,7 +822,6 @@ ${exampleJson}
|
|
|
855
822
|
Now, generate the JSON for the requested question.`;
|
|
856
823
|
}
|
|
857
824
|
async function generateMRQQuestion(clientInput, apiKey) {
|
|
858
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
859
825
|
if (clientInput.minCorrectAnswers > clientInput.maxCorrectAnswers) {
|
|
860
826
|
return { error: `Invalid input: minCorrectAnswers (${clientInput.minCorrectAnswers}) cannot be greater than maxCorrectAnswers (${clientInput.maxCorrectAnswers}).` };
|
|
861
827
|
}
|
|
@@ -878,7 +844,7 @@ async function generateMRQQuestion(clientInput, apiKey) {
|
|
|
878
844
|
const promptText = buildEnhancedPrompt4(clientInput, attempt);
|
|
879
845
|
const promptHash = Buffer.from(promptText).toString("base64").slice(0, 10);
|
|
880
846
|
try {
|
|
881
|
-
DebugLogger.logPrompt(attempt, promptText,
|
|
847
|
+
DebugLogger.logPrompt(attempt, promptText, { ...clientInput, attemptNumber: attempt, promptHash });
|
|
882
848
|
const parts = [{ text: promptText }];
|
|
883
849
|
if (clientInput.imageUrl) {
|
|
884
850
|
const mimeType = clientInput.imageUrl.endsWith(".png") ? "image/png" : "image/jpeg";
|
|
@@ -892,7 +858,7 @@ async function generateMRQQuestion(clientInput, apiKey) {
|
|
|
892
858
|
contents
|
|
893
859
|
});
|
|
894
860
|
const response = aiResult;
|
|
895
|
-
const rawText =
|
|
861
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
896
862
|
const duration = Date.now() - startTime;
|
|
897
863
|
DebugLogger.logResponse(attempt, rawText);
|
|
898
864
|
if (!rawText) {
|
|
@@ -909,8 +875,8 @@ async function generateMRQQuestion(clientInput, apiKey) {
|
|
|
909
875
|
if (correctCount < clientInput.minCorrectAnswers || correctCount > clientInput.maxCorrectAnswers) {
|
|
910
876
|
throw new Error(`AI provided ${correctCount} correct answers, which is outside the required range of ${clientInput.minCorrectAnswers}-${clientInput.maxCorrectAnswers}.`);
|
|
911
877
|
}
|
|
912
|
-
if (
|
|
913
|
-
const verifiedCategory =
|
|
878
|
+
if (clientInput.quizContext?.originalCategory) {
|
|
879
|
+
const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
|
|
914
880
|
const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
|
|
915
881
|
if (verifiedCategory && verifiedCategory !== requiredCategory) {
|
|
916
882
|
throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
|
|
@@ -939,13 +905,13 @@ async function generateMRQQuestion(clientInput, apiKey) {
|
|
|
939
905
|
correctAnswerIds: finalCorrectAnswerIds,
|
|
940
906
|
explanation: aiGeneratedContent.explanation,
|
|
941
907
|
points: aiGeneratedContent.points,
|
|
942
|
-
topic: aiGeneratedContent.topic ||
|
|
908
|
+
topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
|
|
943
909
|
difficulty: aiGeneratedContent.difficulty || clientInput.difficulty,
|
|
944
|
-
contextCode:
|
|
945
|
-
bloomLevel:
|
|
946
|
-
learningObjective:
|
|
947
|
-
subject:
|
|
948
|
-
category:
|
|
910
|
+
contextCode: clientInput.quizContext?.plannedContextId,
|
|
911
|
+
bloomLevel: clientInput.quizContext?.plannedBloomLevel,
|
|
912
|
+
learningObjective: clientInput.quizContext?.originalLoId,
|
|
913
|
+
subject: clientInput.quizContext?.originalSubject,
|
|
914
|
+
category: clientInput.quizContext?.originalCategory,
|
|
949
915
|
imageUrl: clientInput.imageUrl
|
|
950
916
|
};
|
|
951
917
|
const validatedQuestion = MultipleResponseQuestionZodSchema.parse(completeQuestion);
|
|
@@ -967,7 +933,7 @@ async function generateMRQQuestion(clientInput, apiKey) {
|
|
|
967
933
|
}
|
|
968
934
|
}
|
|
969
935
|
DebugLogger.logAttemptSummary(attemptResults);
|
|
970
|
-
const errorMessage = `Failed to generate MRQ question after ${MAX_RETRY_ATTEMPTS4} attempts. Last error: ${lastError
|
|
936
|
+
const errorMessage = `Failed to generate MRQ question after ${MAX_RETRY_ATTEMPTS4} attempts. Last error: ${lastError?.message}`;
|
|
971
937
|
console.error("\n\u274C Final Result: FAILED");
|
|
972
938
|
console.error(errorMessage);
|
|
973
939
|
return { error: errorMessage };
|
|
@@ -997,7 +963,7 @@ var MAX_RETRY_ATTEMPTS5 = 3;
|
|
|
997
963
|
var RETRY_DELAY_MS5 = 3e3;
|
|
998
964
|
function buildEnhancedPrompt5(clientInput, attemptNumber) {
|
|
999
965
|
const { quizContext, language, difficulty, minRange, maxRange, allowDecimals, imageUrl } = clientInput;
|
|
1000
|
-
const category =
|
|
966
|
+
const category = quizContext?.originalCategory || "the specified technical category";
|
|
1001
967
|
const attemptInfo = attemptNumber > 1 ? `
|
|
1002
968
|
## DEBUG INFO - This is attempt #${attemptNumber}
|
|
1003
969
|
Previous attempts failed. Ensure the 'answer' is a valid number and fits within the specified constraints.
|
|
@@ -1006,10 +972,10 @@ Previous attempts failed. Ensure the 'answer' is a valid number and fits within
|
|
|
1006
972
|
const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and its numerical answer must be directly related to the content of this image.` : "";
|
|
1007
973
|
const contextStrings = [
|
|
1008
974
|
`**Required Category:** ${category}`,
|
|
1009
|
-
|
|
975
|
+
quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
|
|
1010
976
|
imageContextInstruction,
|
|
1011
|
-
|
|
1012
|
-
|
|
977
|
+
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
978
|
+
quizContext?.targetMisconception && `**Target Misconception:** The question should clarify this numerical error: "${quizContext.targetMisconception}"`
|
|
1013
979
|
].filter(Boolean).map((s) => `- ${s}`).join("\n");
|
|
1014
980
|
const constraintStrings = [
|
|
1015
981
|
minRange !== void 0 && `The final 'answer' MUST be greater than or equal to ${minRange}.`,
|
|
@@ -1041,7 +1007,7 @@ ${contextStrings}
|
|
|
1041
1007
|
Based on all the rules and context above, generate a single Numeric Question.
|
|
1042
1008
|
|
|
1043
1009
|
### Input Parameters & Constraints
|
|
1044
|
-
- **Topic for Question:** ${
|
|
1010
|
+
- **Topic for Question:** ${quizContext?.plannedTopic || "General"}
|
|
1045
1011
|
- **Language for Text:** ${language}
|
|
1046
1012
|
- **Difficulty Level:** ${difficulty}
|
|
1047
1013
|
${constraintStrings ? `
|
|
@@ -1056,7 +1022,6 @@ ${exampleJson}
|
|
|
1056
1022
|
Now, generate the JSON for the requested question.`;
|
|
1057
1023
|
}
|
|
1058
1024
|
async function generateNumericQuestion(clientInput, apiKey) {
|
|
1059
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
|
|
1060
1025
|
if (clientInput.minRange !== void 0 && clientInput.maxRange !== void 0 && clientInput.minRange > clientInput.maxRange) {
|
|
1061
1026
|
return { error: `Invalid input: minRange (${clientInput.minRange}) cannot be greater than maxRange (${clientInput.maxRange}).` };
|
|
1062
1027
|
}
|
|
@@ -1077,7 +1042,7 @@ async function generateNumericQuestion(clientInput, apiKey) {
|
|
|
1077
1042
|
const promptText = buildEnhancedPrompt5(clientInput, attempt);
|
|
1078
1043
|
const promptHash = Buffer.from(promptText).toString("base64").slice(0, 10);
|
|
1079
1044
|
try {
|
|
1080
|
-
DebugLogger.logPrompt(attempt, promptText,
|
|
1045
|
+
DebugLogger.logPrompt(attempt, promptText, { ...clientInput, attemptNumber: attempt, promptHash });
|
|
1081
1046
|
const parts = [{ text: promptText }];
|
|
1082
1047
|
if (clientInput.imageUrl) {
|
|
1083
1048
|
const mimeType = clientInput.imageUrl.endsWith(".png") ? "image/png" : "image/jpeg";
|
|
@@ -1087,7 +1052,7 @@ async function generateNumericQuestion(clientInput, apiKey) {
|
|
|
1087
1052
|
const contents = [{ role: "user", parts }];
|
|
1088
1053
|
const aiResult = await ai.models.generateContent({ model, config, contents });
|
|
1089
1054
|
const response = aiResult;
|
|
1090
|
-
const rawText =
|
|
1055
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
1091
1056
|
const duration = Date.now() - startTime;
|
|
1092
1057
|
DebugLogger.logResponse(attempt, rawText);
|
|
1093
1058
|
if (!rawText) throw new Error("AI returned an empty response.");
|
|
@@ -1105,8 +1070,8 @@ async function generateNumericQuestion(clientInput, apiKey) {
|
|
|
1105
1070
|
if (!clientInput.allowDecimals && !Number.isInteger(answer)) {
|
|
1106
1071
|
throw new Error(`AI answer ${answer} is not an integer, but decimals are not allowed.`);
|
|
1107
1072
|
}
|
|
1108
|
-
if (
|
|
1109
|
-
const verifiedCategory =
|
|
1073
|
+
if (clientInput.quizContext?.originalCategory) {
|
|
1074
|
+
const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
|
|
1110
1075
|
const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
|
|
1111
1076
|
if (verifiedCategory && verifiedCategory !== requiredCategory) {
|
|
1112
1077
|
throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
|
|
@@ -1117,16 +1082,16 @@ async function generateNumericQuestion(clientInput, apiKey) {
|
|
|
1117
1082
|
questionType: "numeric",
|
|
1118
1083
|
prompt: aiGeneratedContent.prompt,
|
|
1119
1084
|
answer: aiGeneratedContent.answer,
|
|
1120
|
-
tolerance:
|
|
1085
|
+
tolerance: clientInput.tolerance ?? aiGeneratedContent.tolerance ?? 0,
|
|
1121
1086
|
explanation: aiGeneratedContent.explanation,
|
|
1122
1087
|
points: aiGeneratedContent.points,
|
|
1123
|
-
topic: aiGeneratedContent.topic ||
|
|
1088
|
+
topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
|
|
1124
1089
|
difficulty: clientInput.difficulty,
|
|
1125
|
-
contextCode:
|
|
1126
|
-
bloomLevel:
|
|
1127
|
-
learningObjective:
|
|
1128
|
-
subject:
|
|
1129
|
-
category:
|
|
1090
|
+
contextCode: clientInput.quizContext?.plannedContextId,
|
|
1091
|
+
bloomLevel: clientInput.quizContext?.plannedBloomLevel,
|
|
1092
|
+
learningObjective: clientInput.quizContext?.originalLoId,
|
|
1093
|
+
subject: clientInput.quizContext?.originalSubject,
|
|
1094
|
+
category: clientInput.quizContext?.originalCategory,
|
|
1130
1095
|
imageUrl: clientInput.imageUrl
|
|
1131
1096
|
};
|
|
1132
1097
|
const validatedQuestion = NumericQuestionZodSchema.parse(completeQuestion);
|
|
@@ -1148,7 +1113,7 @@ async function generateNumericQuestion(clientInput, apiKey) {
|
|
|
1148
1113
|
}
|
|
1149
1114
|
}
|
|
1150
1115
|
DebugLogger.logAttemptSummary(attemptResults);
|
|
1151
|
-
const errorMessage = `Failed to generate Numeric question after ${MAX_RETRY_ATTEMPTS5} attempts. Last error: ${lastError
|
|
1116
|
+
const errorMessage = `Failed to generate Numeric question after ${MAX_RETRY_ATTEMPTS5} attempts. Last error: ${lastError?.message}`;
|
|
1152
1117
|
console.error("\n\u274C Final Result: FAILED");
|
|
1153
1118
|
console.error(errorMessage);
|
|
1154
1119
|
return { error: errorMessage };
|
|
@@ -1174,7 +1139,7 @@ var MAX_RETRY_ATTEMPTS6 = 3;
|
|
|
1174
1139
|
var RETRY_DELAY_MS6 = 3e3;
|
|
1175
1140
|
function buildEnhancedPrompt6(clientInput, attemptNumber) {
|
|
1176
1141
|
const { quizContext, language, difficulty, numberOfItems, imageUrl } = clientInput;
|
|
1177
|
-
const category =
|
|
1142
|
+
const category = quizContext?.originalCategory || "the specified technical category";
|
|
1178
1143
|
const attemptInfo = attemptNumber > 1 ? `
|
|
1179
1144
|
## DEBUG INFO - This is attempt #${attemptNumber}
|
|
1180
1145
|
Previous attempts failed. Ensure the 'itemsInCorrectOrder' array has exactly the required number of items and the JSON is valid.
|
|
@@ -1183,10 +1148,10 @@ Previous attempts failed. Ensure the 'itemsInCorrectOrder' array has exactly the
|
|
|
1183
1148
|
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.` : "";
|
|
1184
1149
|
const contextStrings = [
|
|
1185
1150
|
`**Required Category:** ${category}`,
|
|
1186
|
-
|
|
1151
|
+
quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
|
|
1187
1152
|
imageContextInstruction,
|
|
1188
|
-
|
|
1189
|
-
|
|
1153
|
+
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
1154
|
+
quizContext?.targetMisconception && `**Target Misconception:** The sequence should clarify this specific process error: "${quizContext.targetMisconception}"`
|
|
1190
1155
|
].filter(Boolean).map((s) => `- ${s}`).join("\n");
|
|
1191
1156
|
const exampleJson = JSON.stringify({
|
|
1192
1157
|
prompt: "Arrange the steps to make a network request in Swift using URLSession.",
|
|
@@ -1217,7 +1182,7 @@ ${contextStrings}
|
|
|
1217
1182
|
Based on all the rules and context above, generate a single Sequence Question.
|
|
1218
1183
|
|
|
1219
1184
|
### Input Parameters
|
|
1220
|
-
- **Topic for Question:** ${
|
|
1185
|
+
- **Topic for Question:** ${quizContext?.plannedTopic || "General"}
|
|
1221
1186
|
- **Language for Text:** ${language}
|
|
1222
1187
|
- **Difficulty Level:** ${difficulty}
|
|
1223
1188
|
- **Number of Items:** Generate an array 'itemsInCorrectOrder' containing exactly ${numberOfItems} items. The array itself MUST be in the correct final sequence.
|
|
@@ -1230,7 +1195,6 @@ ${exampleJson}
|
|
|
1230
1195
|
Now, generate the JSON for the requested question.`;
|
|
1231
1196
|
}
|
|
1232
1197
|
async function generateSequenceQuestion(clientInput, apiKey) {
|
|
1233
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
1234
1198
|
const ai = new genai.GoogleGenAI({ apiKey });
|
|
1235
1199
|
const model = "gemini-2.5-flash";
|
|
1236
1200
|
const config = {
|
|
@@ -1247,7 +1211,7 @@ async function generateSequenceQuestion(clientInput, apiKey) {
|
|
|
1247
1211
|
const promptText = buildEnhancedPrompt6(clientInput, attempt);
|
|
1248
1212
|
const promptHash = Buffer.from(promptText).toString("base64").slice(0, 10);
|
|
1249
1213
|
try {
|
|
1250
|
-
DebugLogger.logPrompt(attempt, promptText,
|
|
1214
|
+
DebugLogger.logPrompt(attempt, promptText, { ...clientInput, attemptNumber: attempt, promptHash });
|
|
1251
1215
|
const parts = [{ text: promptText }];
|
|
1252
1216
|
if (clientInput.imageUrl) {
|
|
1253
1217
|
const mimeType = clientInput.imageUrl.endsWith(".png") ? "image/png" : "image/jpeg";
|
|
@@ -1257,7 +1221,7 @@ async function generateSequenceQuestion(clientInput, apiKey) {
|
|
|
1257
1221
|
const contents = [{ role: "user", parts }];
|
|
1258
1222
|
const aiResult = await ai.models.generateContent({ model, config, contents });
|
|
1259
1223
|
const response = aiResult;
|
|
1260
|
-
const rawText =
|
|
1224
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
1261
1225
|
const duration = Date.now() - startTime;
|
|
1262
1226
|
DebugLogger.logResponse(attempt, rawText);
|
|
1263
1227
|
if (!rawText) throw new Error("AI returned an empty response.");
|
|
@@ -1268,8 +1232,8 @@ async function generateSequenceQuestion(clientInput, apiKey) {
|
|
|
1268
1232
|
if (aiGeneratedContent.itemsInCorrectOrder.length !== clientInput.numberOfItems) {
|
|
1269
1233
|
throw new Error(`AI generated ${aiGeneratedContent.itemsInCorrectOrder.length} items, but ${clientInput.numberOfItems} were required.`);
|
|
1270
1234
|
}
|
|
1271
|
-
if (
|
|
1272
|
-
const verifiedCategory =
|
|
1235
|
+
if (clientInput.quizContext?.originalCategory) {
|
|
1236
|
+
const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
|
|
1273
1237
|
const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
|
|
1274
1238
|
if (verifiedCategory && verifiedCategory !== requiredCategory) {
|
|
1275
1239
|
throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
|
|
@@ -1290,13 +1254,13 @@ async function generateSequenceQuestion(clientInput, apiKey) {
|
|
|
1290
1254
|
correctOrder: finalCorrectOrder,
|
|
1291
1255
|
explanation: aiGeneratedContent.explanation,
|
|
1292
1256
|
points: aiGeneratedContent.points,
|
|
1293
|
-
topic: aiGeneratedContent.topic ||
|
|
1257
|
+
topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
|
|
1294
1258
|
difficulty: clientInput.difficulty,
|
|
1295
|
-
contextCode:
|
|
1296
|
-
bloomLevel:
|
|
1297
|
-
learningObjective:
|
|
1298
|
-
subject:
|
|
1299
|
-
category:
|
|
1259
|
+
contextCode: clientInput.quizContext?.plannedContextId,
|
|
1260
|
+
bloomLevel: clientInput.quizContext?.plannedBloomLevel,
|
|
1261
|
+
learningObjective: clientInput.quizContext?.originalLoId,
|
|
1262
|
+
subject: clientInput.quizContext?.originalSubject,
|
|
1263
|
+
category: clientInput.quizContext?.originalCategory,
|
|
1300
1264
|
imageUrl: clientInput.imageUrl
|
|
1301
1265
|
};
|
|
1302
1266
|
const validatedQuestion = SequenceQuestionZodSchema.parse(completeQuestion);
|
|
@@ -1318,7 +1282,7 @@ async function generateSequenceQuestion(clientInput, apiKey) {
|
|
|
1318
1282
|
}
|
|
1319
1283
|
}
|
|
1320
1284
|
DebugLogger.logAttemptSummary(attemptResults);
|
|
1321
|
-
const errorMessage = `Failed to generate Sequence question after ${MAX_RETRY_ATTEMPTS6} attempts. Last error: ${lastError
|
|
1285
|
+
const errorMessage = `Failed to generate Sequence question after ${MAX_RETRY_ATTEMPTS6} attempts. Last error: ${lastError?.message}`;
|
|
1322
1286
|
console.error("\n\u274C Final Result: FAILED");
|
|
1323
1287
|
console.error(errorMessage);
|
|
1324
1288
|
return { error: errorMessage };
|
|
@@ -1343,7 +1307,7 @@ var MAX_RETRY_ATTEMPTS7 = 3;
|
|
|
1343
1307
|
var RETRY_DELAY_MS7 = 3e3;
|
|
1344
1308
|
function buildEnhancedPrompt7(clientInput, attemptNumber) {
|
|
1345
1309
|
const { quizContext, language, difficulty, imageUrl } = clientInput;
|
|
1346
|
-
const category =
|
|
1310
|
+
const category = quizContext?.originalCategory || "the specified technical category";
|
|
1347
1311
|
const attemptInfo = attemptNumber > 1 ? `
|
|
1348
1312
|
## DEBUG INFO - This is attempt #${attemptNumber}
|
|
1349
1313
|
Previous attempts failed. Ensure 'acceptedAnswers' is a non-empty array of strings and the JSON is valid.
|
|
@@ -1352,10 +1316,10 @@ Previous attempts failed. Ensure 'acceptedAnswers' is a non-empty array of strin
|
|
|
1352
1316
|
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.` : "";
|
|
1353
1317
|
const contextStrings = [
|
|
1354
1318
|
`**Required Category:** ${category}`,
|
|
1355
|
-
|
|
1319
|
+
quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
|
|
1356
1320
|
imageContextInstruction,
|
|
1357
|
-
|
|
1358
|
-
|
|
1321
|
+
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
1322
|
+
quizContext?.targetMisconception && `**Target Misconception:** The question should require an answer that corrects this specific misconception: "${quizContext.targetMisconception}"`
|
|
1359
1323
|
].filter(Boolean).map((s) => `- ${s}`).join("\n");
|
|
1360
1324
|
const exampleJson = JSON.stringify({
|
|
1361
1325
|
prompt: "In Swift, what keyword is used to declare a constant?",
|
|
@@ -1381,7 +1345,7 @@ ${contextStrings}
|
|
|
1381
1345
|
Based on all the rules and context above, generate a single Short Answer Question.
|
|
1382
1346
|
|
|
1383
1347
|
### Input Parameters
|
|
1384
|
-
- **Topic for Question:** ${
|
|
1348
|
+
- **Topic for Question:** ${quizContext?.plannedTopic || "General"}
|
|
1385
1349
|
- **Language for Text:** ${language}
|
|
1386
1350
|
- **Difficulty Level:** ${difficulty}
|
|
1387
1351
|
|
|
@@ -1393,7 +1357,6 @@ ${exampleJson}
|
|
|
1393
1357
|
Now, generate the JSON for the requested question.`;
|
|
1394
1358
|
}
|
|
1395
1359
|
async function generateShortAnswerQuestion(clientInput, apiKey) {
|
|
1396
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
1397
1360
|
const ai = new genai.GoogleGenAI({ apiKey });
|
|
1398
1361
|
const model = "gemini-2.5-flash";
|
|
1399
1362
|
const config = {
|
|
@@ -1410,7 +1373,7 @@ async function generateShortAnswerQuestion(clientInput, apiKey) {
|
|
|
1410
1373
|
const promptText = buildEnhancedPrompt7(clientInput, attempt);
|
|
1411
1374
|
const promptHash = Buffer.from(promptText).toString("base64").slice(0, 10);
|
|
1412
1375
|
try {
|
|
1413
|
-
DebugLogger.logPrompt(attempt, promptText,
|
|
1376
|
+
DebugLogger.logPrompt(attempt, promptText, { ...clientInput, attemptNumber: attempt, promptHash });
|
|
1414
1377
|
const parts = [{ text: promptText }];
|
|
1415
1378
|
if (clientInput.imageUrl) {
|
|
1416
1379
|
const mimeType = clientInput.imageUrl.endsWith(".png") ? "image/png" : "image/jpeg";
|
|
@@ -1420,7 +1383,7 @@ async function generateShortAnswerQuestion(clientInput, apiKey) {
|
|
|
1420
1383
|
const contents = [{ role: "user", parts }];
|
|
1421
1384
|
const aiResult = await ai.models.generateContent({ model, config, contents });
|
|
1422
1385
|
const response = aiResult;
|
|
1423
|
-
const rawText =
|
|
1386
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
1424
1387
|
const duration = Date.now() - startTime;
|
|
1425
1388
|
DebugLogger.logResponse(attempt, rawText);
|
|
1426
1389
|
if (!rawText) throw new Error("AI returned an empty response.");
|
|
@@ -1428,8 +1391,8 @@ async function generateShortAnswerQuestion(clientInput, apiKey) {
|
|
|
1428
1391
|
DebugLogger.logValidation(attempt, "JSON Parsed Successfully", parsedJson);
|
|
1429
1392
|
const aiGeneratedContent = AIShortAnswerOutputFieldsSchema.parse(parsedJson);
|
|
1430
1393
|
DebugLogger.logValidation(attempt, "Zod Schema Validated", aiGeneratedContent);
|
|
1431
|
-
if (
|
|
1432
|
-
const verifiedCategory =
|
|
1394
|
+
if (clientInput.quizContext?.originalCategory) {
|
|
1395
|
+
const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
|
|
1433
1396
|
const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
|
|
1434
1397
|
if (verifiedCategory && verifiedCategory !== requiredCategory) {
|
|
1435
1398
|
throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
|
|
@@ -1443,13 +1406,13 @@ async function generateShortAnswerQuestion(clientInput, apiKey) {
|
|
|
1443
1406
|
isCaseSensitive: clientInput.isCaseSensitive,
|
|
1444
1407
|
explanation: aiGeneratedContent.explanation,
|
|
1445
1408
|
points: aiGeneratedContent.points,
|
|
1446
|
-
topic: aiGeneratedContent.topic ||
|
|
1409
|
+
topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
|
|
1447
1410
|
difficulty: clientInput.difficulty,
|
|
1448
|
-
contextCode:
|
|
1449
|
-
bloomLevel:
|
|
1450
|
-
learningObjective:
|
|
1451
|
-
subject:
|
|
1452
|
-
category:
|
|
1411
|
+
contextCode: clientInput.quizContext?.plannedContextId,
|
|
1412
|
+
bloomLevel: clientInput.quizContext?.plannedBloomLevel,
|
|
1413
|
+
learningObjective: clientInput.quizContext?.originalLoId,
|
|
1414
|
+
subject: clientInput.quizContext?.originalSubject,
|
|
1415
|
+
category: clientInput.quizContext?.originalCategory,
|
|
1453
1416
|
imageUrl: clientInput.imageUrl
|
|
1454
1417
|
};
|
|
1455
1418
|
const validatedQuestion = ShortAnswerQuestionZodSchema.parse(completeQuestion);
|
|
@@ -1471,7 +1434,7 @@ async function generateShortAnswerQuestion(clientInput, apiKey) {
|
|
|
1471
1434
|
}
|
|
1472
1435
|
}
|
|
1473
1436
|
DebugLogger.logAttemptSummary(attemptResults);
|
|
1474
|
-
const errorMessage = `Failed to generate Short Answer question after ${MAX_RETRY_ATTEMPTS7} attempts. Last error: ${lastError
|
|
1437
|
+
const errorMessage = `Failed to generate Short Answer question after ${MAX_RETRY_ATTEMPTS7} attempts. Last error: ${lastError?.message}`;
|
|
1475
1438
|
console.error("\n\u274C Final Result: FAILED");
|
|
1476
1439
|
console.error(errorMessage);
|
|
1477
1440
|
return { error: errorMessage };
|
|
@@ -1493,21 +1456,21 @@ var MAX_RETRY_ATTEMPTS8 = 3;
|
|
|
1493
1456
|
var RETRY_DELAY_MS8 = 3e3;
|
|
1494
1457
|
function buildEnhancedPrompt8(clientInput, attemptNumber) {
|
|
1495
1458
|
const { quizContext, language, difficulty, imageUrl } = clientInput;
|
|
1496
|
-
const category =
|
|
1459
|
+
const category = quizContext?.originalCategory || "the specified technical category";
|
|
1497
1460
|
const attemptInfo = attemptNumber > 1 ? `
|
|
1498
1461
|
## DEBUG INFO - This is attempt #${attemptNumber}
|
|
1499
1462
|
Previous attempts failed. Ensure the JSON is valid and 'correctAnswer' is a boolean.
|
|
1500
1463
|
|
|
1501
1464
|
` : "";
|
|
1502
1465
|
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.` : "";
|
|
1503
|
-
const misconceptionGuidance =
|
|
1466
|
+
const misconceptionGuidance = quizContext?.targetMisconception ? `**Target Misconception:** The statement you create MUST be FALSE and based on this common mistake: "${quizContext.targetMisconception}"` : "";
|
|
1504
1467
|
const contextStrings = [
|
|
1505
1468
|
`**Required Category:** ${category}`,
|
|
1506
|
-
|
|
1469
|
+
quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
|
|
1507
1470
|
imageContextInstruction,
|
|
1508
|
-
|
|
1471
|
+
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
1509
1472
|
misconceptionGuidance,
|
|
1510
|
-
|
|
1473
|
+
quizContext?.difficultyReason && `**Pedagogical Reason:** ${quizContext.difficultyReason}`
|
|
1511
1474
|
].filter(Boolean).map((s) => `- ${s}`).join("\n");
|
|
1512
1475
|
const exampleJson = JSON.stringify({
|
|
1513
1476
|
prompt: "In Swift, you must explicitly unwrap an Optional value before you can use its stored value.",
|
|
@@ -1534,7 +1497,7 @@ ${contextStrings}
|
|
|
1534
1497
|
Based on all the rules and context above, generate a single True/False Question.
|
|
1535
1498
|
|
|
1536
1499
|
### Input Parameters
|
|
1537
|
-
- **Topic for Question:** ${
|
|
1500
|
+
- **Topic for Question:** ${quizContext?.plannedTopic || "General"}
|
|
1538
1501
|
- **Language for Text:** ${language}
|
|
1539
1502
|
- **Difficulty Level:** ${difficulty}
|
|
1540
1503
|
|
|
@@ -1546,7 +1509,6 @@ ${exampleJson}
|
|
|
1546
1509
|
Now, generate the JSON for the requested question.`;
|
|
1547
1510
|
}
|
|
1548
1511
|
async function generateTrueFalseQuestion(clientInput, apiKey) {
|
|
1549
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
1550
1512
|
const ai = new genai.GoogleGenAI({ apiKey });
|
|
1551
1513
|
const model = "gemini-2.5-flash";
|
|
1552
1514
|
const config = {
|
|
@@ -1563,7 +1525,7 @@ async function generateTrueFalseQuestion(clientInput, apiKey) {
|
|
|
1563
1525
|
const promptText = buildEnhancedPrompt8(clientInput, attempt);
|
|
1564
1526
|
const promptHash = Buffer.from(promptText).toString("base64").slice(0, 10);
|
|
1565
1527
|
try {
|
|
1566
|
-
DebugLogger.logPrompt(attempt, promptText,
|
|
1528
|
+
DebugLogger.logPrompt(attempt, promptText, { ...clientInput, attemptNumber: attempt, promptHash });
|
|
1567
1529
|
const parts = [{ text: promptText }];
|
|
1568
1530
|
if (clientInput.imageUrl) {
|
|
1569
1531
|
const mimeType = clientInput.imageUrl.endsWith(".png") ? "image/png" : "image/jpeg";
|
|
@@ -1573,7 +1535,7 @@ async function generateTrueFalseQuestion(clientInput, apiKey) {
|
|
|
1573
1535
|
const contents = [{ role: "user", parts }];
|
|
1574
1536
|
const aiResult = await ai.models.generateContent({ model, config, contents });
|
|
1575
1537
|
const response = aiResult;
|
|
1576
|
-
const rawText =
|
|
1538
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
1577
1539
|
const duration = Date.now() - startTime;
|
|
1578
1540
|
DebugLogger.logResponse(attempt, rawText);
|
|
1579
1541
|
if (!rawText) throw new Error("AI returned an empty response.");
|
|
@@ -1581,11 +1543,11 @@ async function generateTrueFalseQuestion(clientInput, apiKey) {
|
|
|
1581
1543
|
DebugLogger.logValidation(attempt, "JSON Parsed Successfully", parsedJson);
|
|
1582
1544
|
const aiGeneratedContent = AITrueFalseOutputFieldsSchema.parse(parsedJson);
|
|
1583
1545
|
DebugLogger.logValidation(attempt, "Zod Schema Validated", aiGeneratedContent);
|
|
1584
|
-
if (
|
|
1546
|
+
if (clientInput.quizContext?.targetMisconception && aiGeneratedContent.correctAnswer === true) {
|
|
1585
1547
|
throw new Error("AI failed to follow the Misconception Priority rule. The answer should have been false.");
|
|
1586
1548
|
}
|
|
1587
|
-
if (
|
|
1588
|
-
const verifiedCategory =
|
|
1549
|
+
if (clientInput.quizContext?.originalCategory) {
|
|
1550
|
+
const verifiedCategory = aiGeneratedContent.verifiedCategory?.toLowerCase();
|
|
1589
1551
|
const requiredCategory = clientInput.quizContext.originalCategory.toLowerCase();
|
|
1590
1552
|
if (verifiedCategory && verifiedCategory !== requiredCategory) {
|
|
1591
1553
|
throw new Error(`Category mismatch: Required ${requiredCategory}, got ${verifiedCategory}`);
|
|
@@ -1598,13 +1560,13 @@ async function generateTrueFalseQuestion(clientInput, apiKey) {
|
|
|
1598
1560
|
correctAnswer: aiGeneratedContent.correctAnswer,
|
|
1599
1561
|
explanation: aiGeneratedContent.explanation,
|
|
1600
1562
|
points: aiGeneratedContent.points,
|
|
1601
|
-
topic: aiGeneratedContent.topic ||
|
|
1563
|
+
topic: aiGeneratedContent.topic || clientInput.quizContext?.originalTopic,
|
|
1602
1564
|
difficulty: clientInput.difficulty,
|
|
1603
|
-
contextCode:
|
|
1604
|
-
bloomLevel:
|
|
1605
|
-
learningObjective:
|
|
1606
|
-
subject:
|
|
1607
|
-
category:
|
|
1565
|
+
contextCode: clientInput.quizContext?.plannedContextId,
|
|
1566
|
+
bloomLevel: clientInput.quizContext?.plannedBloomLevel,
|
|
1567
|
+
learningObjective: clientInput.quizContext?.originalLoId,
|
|
1568
|
+
subject: clientInput.quizContext?.originalSubject,
|
|
1569
|
+
category: clientInput.quizContext?.originalCategory,
|
|
1608
1570
|
imageUrl: clientInput.imageUrl
|
|
1609
1571
|
};
|
|
1610
1572
|
const validatedQuestion = TrueFalseQuestionZodSchema.parse(completeQuestion);
|
|
@@ -1626,7 +1588,7 @@ async function generateTrueFalseQuestion(clientInput, apiKey) {
|
|
|
1626
1588
|
}
|
|
1627
1589
|
}
|
|
1628
1590
|
DebugLogger.logAttemptSummary(attemptResults);
|
|
1629
|
-
const errorMessage = `Failed to generate True/False question after ${MAX_RETRY_ATTEMPTS8} attempts. Last error: ${lastError
|
|
1591
|
+
const errorMessage = `Failed to generate True/False question after ${MAX_RETRY_ATTEMPTS8} attempts. Last error: ${lastError?.message}`;
|
|
1630
1592
|
console.error("\n\u274C Final Result: FAILED");
|
|
1631
1593
|
console.error(errorMessage);
|
|
1632
1594
|
return { error: errorMessage };
|
|
@@ -1674,10 +1636,10 @@ var JsonRepairEngine = class {
|
|
|
1674
1636
|
if (breakIndex !== -1) {
|
|
1675
1637
|
const stringContent = afterUnterminated.substring(0, breakIndex);
|
|
1676
1638
|
const remainder = afterUnterminated.substring(breakIndex);
|
|
1677
|
-
const escapedContent = stringContent.replace(
|
|
1639
|
+
const escapedContent = stringContent.replace(/(?<!\\)"/g, '\\"');
|
|
1678
1640
|
repaired = beforeUnterminated + escapedContent + '"' + remainder;
|
|
1679
1641
|
} else {
|
|
1680
|
-
const escapedContent = afterUnterminated.replace(
|
|
1642
|
+
const escapedContent = afterUnterminated.replace(/(?<!\\)"/g, '\\"');
|
|
1681
1643
|
repaired = beforeUnterminated + escapedContent + '"';
|
|
1682
1644
|
}
|
|
1683
1645
|
}
|
|
@@ -1753,7 +1715,6 @@ var JsonRepairEngine = class {
|
|
|
1753
1715
|
* Main repair function that attempts multiple strategies.
|
|
1754
1716
|
*/
|
|
1755
1717
|
static repairJson(jsonStr) {
|
|
1756
|
-
var _a;
|
|
1757
1718
|
let current = jsonStr.trim();
|
|
1758
1719
|
const maxAttempts = 5;
|
|
1759
1720
|
let lastError = "";
|
|
@@ -1783,7 +1744,7 @@ var JsonRepairEngine = class {
|
|
|
1783
1744
|
}
|
|
1784
1745
|
lastError = validation.error || "";
|
|
1785
1746
|
lastPosition = validation.position;
|
|
1786
|
-
if (
|
|
1747
|
+
if (validation.error?.includes("Unterminated string")) {
|
|
1787
1748
|
current = this.repairUnterminatedStrings(current);
|
|
1788
1749
|
} else {
|
|
1789
1750
|
current = this.applyCommonFixes(current);
|
|
@@ -2115,7 +2076,6 @@ ENHANCED DIVERSITY & QUALITY ASSURANCE RULES:
|
|
|
2115
2076
|
`;
|
|
2116
2077
|
}
|
|
2117
2078
|
async function generateQuizPlan(clientInput, apiKey, imageContexts = []) {
|
|
2118
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
2119
2079
|
const logger = new QuizPlanLogger();
|
|
2120
2080
|
try {
|
|
2121
2081
|
logger.log("VALIDATION_START", {
|
|
@@ -2251,10 +2211,7 @@ Execute this plan with pedagogical precision. The quiz should feel like a carefu
|
|
|
2251
2211
|
logger.log("PROMPT_PREPARATION", {
|
|
2252
2212
|
promptLength: enhancedPromptText.length,
|
|
2253
2213
|
topicCount: clientInput.topics.length,
|
|
2254
|
-
misconceptionCount: clientInput.topics.reduce((sum, t) =>
|
|
2255
|
-
var _a2;
|
|
2256
|
-
return sum + (((_a2 = t.commonMisconceptions) == null ? void 0 : _a2.length) || 0);
|
|
2257
|
-
}, 0)
|
|
2214
|
+
misconceptionCount: clientInput.topics.reduce((sum, t) => sum + (t.commonMisconceptions?.length || 0), 0)
|
|
2258
2215
|
}, Date.now() - promptStartTime);
|
|
2259
2216
|
const generationStartTime = Date.now();
|
|
2260
2217
|
const contents = [
|
|
@@ -2275,11 +2232,11 @@ Execute this plan with pedagogical precision. The quiz should feel like a carefu
|
|
|
2275
2232
|
const response = aiResult;
|
|
2276
2233
|
const generationDuration = Date.now() - generationStartTime;
|
|
2277
2234
|
logger.log("AI_GENERATION", {
|
|
2278
|
-
responseLength:
|
|
2235
|
+
responseLength: response.candidates?.[0]?.content?.parts?.[0]?.text?.length || 0,
|
|
2279
2236
|
duration: generationDuration
|
|
2280
2237
|
}, generationDuration);
|
|
2281
2238
|
const processingStartTime = Date.now();
|
|
2282
|
-
const rawText =
|
|
2239
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
2283
2240
|
let jsonText = rawText;
|
|
2284
2241
|
if (!rawText.trim().startsWith("{") && !rawText.trim().startsWith("[")) {
|
|
2285
2242
|
jsonText = extractJsonFromMarkdown(rawText);
|
|
@@ -2312,11 +2269,12 @@ Execute this plan with pedagogical precision. The quiz should feel like a carefu
|
|
|
2312
2269
|
questionCount: aiGeneratedContent.quizPlan.length,
|
|
2313
2270
|
codingQuestionCount: codingQuestions.length,
|
|
2314
2271
|
maxConsecutiveType: diversityAnalysis.maxConsecutive,
|
|
2315
|
-
questionTypeDistribution:
|
|
2272
|
+
questionTypeDistribution: aiGeneratedContent.diversityMetrics?.questionTypeDistribution || {}
|
|
2316
2273
|
}, Date.now() - validationStartTime);
|
|
2317
|
-
const finalResult =
|
|
2274
|
+
const finalResult = {
|
|
2275
|
+
...aiGeneratedContent,
|
|
2318
2276
|
logs: logger.getLogs()
|
|
2319
|
-
}
|
|
2277
|
+
};
|
|
2320
2278
|
logger.log("GENERATION_COMPLETE", {
|
|
2321
2279
|
totalDuration: logger.getTotalDuration(),
|
|
2322
2280
|
success: true,
|
|
@@ -2325,8 +2283,8 @@ Execute this plan with pedagogical precision. The quiz should feel like a carefu
|
|
|
2325
2283
|
console.log("\n=== QUIZ PLAN GENERATION SUMMARY ===");
|
|
2326
2284
|
console.log(`\u2705 Successfully generated ${finalResult.quizPlan.length} questions`);
|
|
2327
2285
|
console.log(`\u23F1\uFE0F Total generation time: ${logger.getTotalDuration()}ms`);
|
|
2328
|
-
console.log(`\u{1F3AF} Question types: ${Object.keys(
|
|
2329
|
-
console.log(`\u{1F9E0} Bloom levels: ${Object.keys(
|
|
2286
|
+
console.log(`\u{1F3AF} Question types: ${Object.keys(finalResult.diversityMetrics?.questionTypeDistribution || {}).join(", ")}`);
|
|
2287
|
+
console.log(`\u{1F9E0} Bloom levels: ${Object.keys(finalResult.diversityMetrics?.bloomLevelDistribution || {}).join(", ")}`);
|
|
2330
2288
|
if (numCodingQuestions > 0) {
|
|
2331
2289
|
console.log(`\u{1F4BB} Coding questions: ${codingQuestions.length}/${numCodingQuestions} required`);
|
|
2332
2290
|
}
|
|
@@ -2345,12 +2303,11 @@ Execute this plan with pedagogical precision. The quiz should feel like a carefu
|
|
|
2345
2303
|
}
|
|
2346
2304
|
}
|
|
2347
2305
|
function validateConsecutiveTypes(quizPlan) {
|
|
2348
|
-
var _a, _b;
|
|
2349
2306
|
let maxConsecutive = 1;
|
|
2350
|
-
let maxType =
|
|
2307
|
+
let maxType = quizPlan[0]?.plannedQuestionType || "";
|
|
2351
2308
|
let maxStartIndex = 0;
|
|
2352
2309
|
let currentConsecutive = 1;
|
|
2353
|
-
let currentType =
|
|
2310
|
+
let currentType = quizPlan[0]?.plannedQuestionType || "";
|
|
2354
2311
|
let currentStartIndex = 0;
|
|
2355
2312
|
for (let i = 1; i < quizPlan.length; i++) {
|
|
2356
2313
|
if (quizPlan[i].plannedQuestionType === currentType) {
|
|
@@ -2548,7 +2505,10 @@ var AICodingQuestionOutputSchema = zod.z.object({
|
|
|
2548
2505
|
solutionCode: zod.z.string().describe("A complete, correct model solution in the specified language."),
|
|
2549
2506
|
testCases: zod.z.array(zod.z.object({
|
|
2550
2507
|
input: zod.z.array(zod.z.any()),
|
|
2551
|
-
|
|
2508
|
+
// FIX: Use .refine to make it explicitly non-optional for TypeScript's inference
|
|
2509
|
+
expectedOutput: zod.z.any().refine((val) => val !== void 0, {
|
|
2510
|
+
message: "expectedOutput is required and cannot be undefined."
|
|
2511
|
+
}),
|
|
2552
2512
|
isPublic: zod.z.boolean()
|
|
2553
2513
|
})).min(3, { message: "Must provide at least 3 test cases." }),
|
|
2554
2514
|
verifiedCodingLanguage: zod.z.enum(["cpp", "javascript", "python", "swift", "csharp"]).optional().describe("The programming language this question actually addresses.")
|
|
@@ -2559,19 +2519,19 @@ var MAX_RETRY_ATTEMPTS9 = 3;
|
|
|
2559
2519
|
var RETRY_DELAY_MS9 = 3e3;
|
|
2560
2520
|
function buildEnhancedPrompt9(clientInput, attemptNumber) {
|
|
2561
2521
|
const { quizContext, difficulty, codingLanguage, language, imageUrl } = clientInput;
|
|
2562
|
-
const subject =
|
|
2522
|
+
const subject = quizContext?.originalSubject || codingLanguage;
|
|
2563
2523
|
const attemptInfo = attemptNumber > 1 ? `
|
|
2564
2524
|
## DEBUG INFO - This is attempt #${attemptNumber}
|
|
2565
|
-
Previous attempts failed. Pay strict attention to the JSON schema and all rules.
|
|
2525
|
+
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.
|
|
2566
2526
|
|
|
2567
2527
|
` : "";
|
|
2568
2528
|
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.` : "";
|
|
2569
2529
|
const contextStrings = [
|
|
2570
2530
|
`**Subject:** ${subject}`,
|
|
2571
|
-
|
|
2531
|
+
quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
|
|
2572
2532
|
imageContextInstruction,
|
|
2573
|
-
|
|
2574
|
-
|
|
2533
|
+
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
2534
|
+
quizContext?.targetMisconception && `**Target Misconception:** The problem should test against this common error: "${quizContext.targetMisconception}"`
|
|
2575
2535
|
].filter(Boolean).map((s) => `- ${s}`).join("\n");
|
|
2576
2536
|
const exampleJson = JSON.stringify({
|
|
2577
2537
|
prompt: "Write a function named 'add' that takes two integers and returns their sum.",
|
|
@@ -2591,13 +2551,14 @@ Generate a single, high-quality Coding question.
|
|
|
2591
2551
|
1. **Language Purity:** All code ('functionSignature', 'solutionCode') MUST be in **${codingLanguage}**.
|
|
2592
2552
|
2. **Context Adherence:** The problem MUST be directly related to the provided context.
|
|
2593
2553
|
3. **Format Integrity:** You MUST return ONLY a single, valid JSON object.
|
|
2554
|
+
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.
|
|
2594
2555
|
|
|
2595
2556
|
## CRITICAL CONTEXT FOR THIS QUESTION
|
|
2596
2557
|
${contextStrings}
|
|
2597
2558
|
|
|
2598
2559
|
## Task: Generate the Question
|
|
2599
2560
|
### Input Parameters
|
|
2600
|
-
- **Topic for Question:** ${
|
|
2561
|
+
- **Topic for Question:** ${quizContext?.plannedTopic || "General"}
|
|
2601
2562
|
- **Natural Language for Text:** ${language}
|
|
2602
2563
|
- **Coding Language:** ${codingLanguage}
|
|
2603
2564
|
- **Difficulty Level:** ${difficulty}
|
|
@@ -2610,7 +2571,6 @@ ${exampleJson}
|
|
|
2610
2571
|
Now, generate the JSON for the requested question.`;
|
|
2611
2572
|
}
|
|
2612
2573
|
async function generateCodingQuestion(clientInput, apiKey) {
|
|
2613
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2614
2574
|
const ai = new genai.GoogleGenAI({ apiKey });
|
|
2615
2575
|
const model = "gemini-2.5-flash";
|
|
2616
2576
|
const config = {
|
|
@@ -2627,7 +2587,7 @@ async function generateCodingQuestion(clientInput, apiKey) {
|
|
|
2627
2587
|
const promptText = buildEnhancedPrompt9(clientInput, attempt);
|
|
2628
2588
|
const promptHash = Buffer.from(promptText).toString("base64").slice(0, 10);
|
|
2629
2589
|
try {
|
|
2630
|
-
DebugLogger.logPrompt(attempt, promptText,
|
|
2590
|
+
DebugLogger.logPrompt(attempt, promptText, { ...clientInput, attemptNumber: attempt, promptHash });
|
|
2631
2591
|
const parts = [{ text: promptText }];
|
|
2632
2592
|
if (clientInput.imageUrl) {
|
|
2633
2593
|
const mimeType = clientInput.imageUrl.endsWith(".png") ? "image/png" : "image/jpeg";
|
|
@@ -2637,7 +2597,7 @@ async function generateCodingQuestion(clientInput, apiKey) {
|
|
|
2637
2597
|
const contents = [{ role: "user", parts }];
|
|
2638
2598
|
const aiResult = await ai.models.generateContent({ model, config, contents });
|
|
2639
2599
|
const response = aiResult;
|
|
2640
|
-
const rawText =
|
|
2600
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
2641
2601
|
const duration = Date.now() - startTime;
|
|
2642
2602
|
DebugLogger.logResponse(attempt, rawText);
|
|
2643
2603
|
if (!rawText) throw new Error("AI returned an empty response.");
|
|
@@ -2648,42 +2608,36 @@ async function generateCodingQuestion(clientInput, apiKey) {
|
|
|
2648
2608
|
if (aiGeneratedContent.verifiedCodingLanguage && aiGeneratedContent.verifiedCodingLanguage !== clientInput.codingLanguage) {
|
|
2649
2609
|
throw new Error(`Language mismatch: Required ${clientInput.codingLanguage}, but AI generated for ${aiGeneratedContent.verifiedCodingLanguage}.`);
|
|
2650
2610
|
}
|
|
2651
|
-
const testCases = aiGeneratedContent.testCases.map((tc) => {
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
input: tc.input,
|
|
2658
|
-
expectedOutput: tc.expectedOutput,
|
|
2659
|
-
isPublic: tc.isPublic
|
|
2660
|
-
};
|
|
2661
|
-
});
|
|
2611
|
+
const testCases = aiGeneratedContent.testCases.map((tc) => ({
|
|
2612
|
+
id: generateUniqueId("tc_"),
|
|
2613
|
+
input: tc.input,
|
|
2614
|
+
expectedOutput: tc.expectedOutput,
|
|
2615
|
+
isPublic: tc.isPublic
|
|
2616
|
+
}));
|
|
2662
2617
|
const completeQuestion = {
|
|
2663
2618
|
id: generateUniqueId("coding_"),
|
|
2664
2619
|
questionType: "coding",
|
|
2665
2620
|
prompt: aiGeneratedContent.prompt,
|
|
2666
2621
|
codingLanguage: clientInput.codingLanguage,
|
|
2667
|
-
// FIXED: Correct field name
|
|
2668
2622
|
functionSignature: aiGeneratedContent.functionSignature,
|
|
2669
2623
|
solutionCode: aiGeneratedContent.solutionCode,
|
|
2670
2624
|
testCases,
|
|
2671
2625
|
points: 25,
|
|
2672
|
-
topic:
|
|
2626
|
+
topic: clientInput.quizContext?.originalTopic,
|
|
2673
2627
|
difficulty: clientInput.difficulty,
|
|
2674
|
-
contextCode:
|
|
2675
|
-
bloomLevel:
|
|
2676
|
-
learningObjective:
|
|
2677
|
-
subject:
|
|
2678
|
-
category:
|
|
2628
|
+
contextCode: clientInput.quizContext?.plannedContextId,
|
|
2629
|
+
bloomLevel: clientInput.quizContext?.plannedBloomLevel,
|
|
2630
|
+
learningObjective: clientInput.quizContext?.originalLoId,
|
|
2631
|
+
subject: clientInput.quizContext?.originalSubject,
|
|
2632
|
+
category: clientInput.quizContext?.originalCategory,
|
|
2679
2633
|
imageUrl: clientInput.imageUrl
|
|
2680
2634
|
};
|
|
2681
|
-
|
|
2635
|
+
CodingQuestionZodSchema.parse(completeQuestion);
|
|
2682
2636
|
attemptResults.push({ success: true, duration, promptLength: promptText.length, responseLength: rawText.length, promptHash });
|
|
2683
2637
|
console.log(`
|
|
2684
2638
|
\u2705 Coding question generation successful on attempt ${attempt} (${duration}ms)`);
|
|
2685
2639
|
if (attempt > 1) DebugLogger.logAttemptSummary(attemptResults);
|
|
2686
|
-
return { question:
|
|
2640
|
+
return { question: completeQuestion };
|
|
2687
2641
|
} catch (error) {
|
|
2688
2642
|
lastError = error;
|
|
2689
2643
|
const duration = Date.now() - startTime;
|
|
@@ -2697,7 +2651,7 @@ async function generateCodingQuestion(clientInput, apiKey) {
|
|
|
2697
2651
|
}
|
|
2698
2652
|
}
|
|
2699
2653
|
DebugLogger.logAttemptSummary(attemptResults);
|
|
2700
|
-
const errorMessage = `Failed to generate Coding question after ${MAX_RETRY_ATTEMPTS9} attempts. Last error: ${lastError
|
|
2654
|
+
const errorMessage = `Failed to generate Coding question after ${MAX_RETRY_ATTEMPTS9} attempts. Last error: ${lastError?.message}`;
|
|
2701
2655
|
console.error("\n\u274C Final Result: FAILED");
|
|
2702
2656
|
console.error(errorMessage);
|
|
2703
2657
|
return { error: errorMessage };
|
|
@@ -2736,7 +2690,6 @@ var calculateCombinedDifficulty = (plannedQ) => {
|
|
|
2736
2690
|
return "hard";
|
|
2737
2691
|
};
|
|
2738
2692
|
async function generateQuestionsFromQuizPlan(clientInput, apiKey) {
|
|
2739
|
-
var _a, _b;
|
|
2740
2693
|
const { quizPlan, language, imageContexts } = clientInput;
|
|
2741
2694
|
const generatedQuestions = [];
|
|
2742
2695
|
const errors = [];
|
|
@@ -2760,9 +2713,9 @@ async function generateQuestionsFromQuizPlan(clientInput, apiKey) {
|
|
|
2760
2713
|
originalSubject: plannedQ.originalSubject,
|
|
2761
2714
|
originalCategory: plannedQ.originalCategory,
|
|
2762
2715
|
originalTopic: plannedQ.originalTopic,
|
|
2763
|
-
loDescription:
|
|
2716
|
+
loDescription: fullLO?.loDescription || plannedQ.plannedTopic
|
|
2764
2717
|
};
|
|
2765
|
-
const imageUrl = plannedQ.imageId && imageContexts ?
|
|
2718
|
+
const imageUrl = plannedQ.imageId && imageContexts ? imageContexts.find((ctx) => ctx.id === plannedQ.imageId)?.imageUrl : void 0;
|
|
2766
2719
|
const baseClientInput = {
|
|
2767
2720
|
language,
|
|
2768
2721
|
difficulty: calculateCombinedDifficulty(plannedQ),
|
|
@@ -2775,35 +2728,36 @@ async function generateQuestionsFromQuizPlan(clientInput, apiKey) {
|
|
|
2775
2728
|
result = await generateTrueFalseQuestion(baseClientInput, apiKey);
|
|
2776
2729
|
break;
|
|
2777
2730
|
case "multiple_choice":
|
|
2778
|
-
result = await generateMCQQuestion(
|
|
2731
|
+
result = await generateMCQQuestion({ ...baseClientInput, numberOfOptions: 4 }, apiKey);
|
|
2779
2732
|
break;
|
|
2780
2733
|
case "multiple_response":
|
|
2781
|
-
result = await generateMRQQuestion(
|
|
2734
|
+
result = await generateMRQQuestion({ ...baseClientInput, numberOfOptions: 5, minCorrectAnswers: 2, maxCorrectAnswers: 3 }, apiKey);
|
|
2782
2735
|
break;
|
|
2783
2736
|
case "short_answer":
|
|
2784
|
-
result = await generateShortAnswerQuestion(
|
|
2737
|
+
result = await generateShortAnswerQuestion({ ...baseClientInput, isCaseSensitive: false }, apiKey);
|
|
2785
2738
|
break;
|
|
2786
2739
|
case "numeric":
|
|
2787
|
-
result = await generateNumericQuestion(
|
|
2740
|
+
result = await generateNumericQuestion({ ...baseClientInput, allowDecimals: true, tolerance: 0 }, apiKey);
|
|
2788
2741
|
break;
|
|
2789
2742
|
case "fill_in_the_blanks":
|
|
2790
|
-
result = await generateFillInTheBlanksQuestion(
|
|
2743
|
+
result = await generateFillInTheBlanksQuestion({ ...baseClientInput, numberOfBlanks: 2, isCaseSensitive: false }, apiKey);
|
|
2791
2744
|
break;
|
|
2792
2745
|
case "sequence":
|
|
2793
|
-
result = await generateSequenceQuestion(
|
|
2746
|
+
result = await generateSequenceQuestion({ ...baseClientInput, numberOfItems: 4 }, apiKey);
|
|
2794
2747
|
break;
|
|
2795
2748
|
case "matching":
|
|
2796
|
-
result = await generateMatchingQuestion(
|
|
2749
|
+
result = await generateMatchingQuestion({ ...baseClientInput, numberOfPairs: 4, shuffleOptions: true }, apiKey);
|
|
2797
2750
|
break;
|
|
2798
2751
|
case "coding": {
|
|
2799
|
-
const subject =
|
|
2752
|
+
const subject = plannedQ.originalSubject?.toLowerCase() || "";
|
|
2800
2753
|
let codingLanguage = "javascript";
|
|
2801
2754
|
if (subject.includes("swift")) codingLanguage = "swift";
|
|
2802
2755
|
else if (subject.includes("python")) codingLanguage = "python";
|
|
2803
|
-
result = await generateCodingQuestion(
|
|
2756
|
+
result = await generateCodingQuestion({
|
|
2757
|
+
...baseClientInput,
|
|
2804
2758
|
codingLanguage,
|
|
2805
|
-
|
|
2806
|
-
}
|
|
2759
|
+
language: language || "English"
|
|
2760
|
+
}, apiKey);
|
|
2807
2761
|
break;
|
|
2808
2762
|
}
|
|
2809
2763
|
default:
|
|
@@ -3540,19 +3494,17 @@ Now, generate the JSON response.`;
|
|
|
3540
3494
|
console.warn(`Skipping MCQ due to invalid correctTempOptionId: ${correctTempId}`);
|
|
3541
3495
|
continue;
|
|
3542
3496
|
}
|
|
3543
|
-
finalQuestion =
|
|
3497
|
+
finalQuestion = {
|
|
3498
|
+
...rawQuestion,
|
|
3544
3499
|
id: questionId,
|
|
3545
|
-
options: finalOptions.map((
|
|
3546
|
-
var _b = _a, { tempId } = _b, rest = __objRest(_b, ["tempId"]);
|
|
3547
|
-
return rest;
|
|
3548
|
-
}),
|
|
3500
|
+
options: finalOptions.map(({ tempId, ...rest }) => rest),
|
|
3549
3501
|
correctAnswerId: correctFinalOption.id
|
|
3550
|
-
}
|
|
3502
|
+
};
|
|
3551
3503
|
break;
|
|
3552
3504
|
}
|
|
3553
3505
|
case "true_false":
|
|
3554
3506
|
case "short_answer": {
|
|
3555
|
-
finalQuestion =
|
|
3507
|
+
finalQuestion = { ...rawQuestion, id: questionId };
|
|
3556
3508
|
break;
|
|
3557
3509
|
}
|
|
3558
3510
|
default:
|