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