@thanh01.pmt/interactive-quiz-kit 1.0.23 → 1.0.25
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-ecosystem-BJ5RR5Ys.d.ts +228 -0
- package/dist/ai-ecosystem-CL30v1Lg.d.cts +228 -0
- package/dist/ai.cjs +181 -227
- package/dist/ai.d.cts +1881 -0
- package/dist/ai.d.ts +1881 -0
- package/dist/ai.js +181 -227
- package/dist/authoring.cjs +6401 -2528
- package/dist/authoring.d.cts +12 -0
- package/dist/authoring.d.ts +12 -0
- package/dist/authoring.js +6281 -2425
- package/dist/index.cjs +374 -174
- package/dist/index.d.cts +444 -0
- package/dist/index.d.ts +444 -0
- package/dist/index.js +373 -175
- package/dist/player.cjs +1491 -1086
- package/dist/player.d.cts +13 -0
- package/dist/player.d.ts +13 -0
- package/dist/player.js +1424 -1019
- package/dist/quiz-config-1gNNhljP.d.cts +197 -0
- package/dist/quiz-config-1gNNhljP.d.ts +197 -0
- package/dist/react-ui.cjs +8621 -3688
- package/dist/react-ui.d.cts +207 -0
- package/dist/react-ui.d.ts +207 -0
- package/dist/react-ui.js +8321 -3404
- package/dist/toaster-CtnxWhfE.d.ts +133 -0
- package/dist/toaster-DUq851l_.d.cts +133 -0
- package/package.json +13 -11
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) {
|
|
@@ -2559,7 +2516,7 @@ var MAX_RETRY_ATTEMPTS9 = 3;
|
|
|
2559
2516
|
var RETRY_DELAY_MS9 = 3e3;
|
|
2560
2517
|
function buildEnhancedPrompt9(clientInput, attemptNumber) {
|
|
2561
2518
|
const { quizContext, difficulty, codingLanguage, language, imageUrl } = clientInput;
|
|
2562
|
-
const subject =
|
|
2519
|
+
const subject = quizContext?.originalSubject || codingLanguage;
|
|
2563
2520
|
const attemptInfo = attemptNumber > 1 ? `
|
|
2564
2521
|
## DEBUG INFO - This is attempt #${attemptNumber}
|
|
2565
2522
|
Previous attempts failed. Pay strict attention to the JSON schema and all rules.
|
|
@@ -2568,10 +2525,10 @@ Previous attempts failed. Pay strict attention to the JSON schema and all rules.
|
|
|
2568
2525
|
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
2526
|
const contextStrings = [
|
|
2570
2527
|
`**Subject:** ${subject}`,
|
|
2571
|
-
|
|
2528
|
+
quizContext?.loDescription && `**Learning Objective:** ${quizContext.loDescription}`,
|
|
2572
2529
|
imageContextInstruction,
|
|
2573
|
-
|
|
2574
|
-
|
|
2530
|
+
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
2531
|
+
quizContext?.targetMisconception && `**Target Misconception:** The problem should test against this common error: "${quizContext.targetMisconception}"`
|
|
2575
2532
|
].filter(Boolean).map((s) => `- ${s}`).join("\n");
|
|
2576
2533
|
const exampleJson = JSON.stringify({
|
|
2577
2534
|
prompt: "Write a function named 'add' that takes two integers and returns their sum.",
|
|
@@ -2597,7 +2554,7 @@ ${contextStrings}
|
|
|
2597
2554
|
|
|
2598
2555
|
## Task: Generate the Question
|
|
2599
2556
|
### Input Parameters
|
|
2600
|
-
- **Topic for Question:** ${
|
|
2557
|
+
- **Topic for Question:** ${quizContext?.plannedTopic || "General"}
|
|
2601
2558
|
- **Natural Language for Text:** ${language}
|
|
2602
2559
|
- **Coding Language:** ${codingLanguage}
|
|
2603
2560
|
- **Difficulty Level:** ${difficulty}
|
|
@@ -2610,7 +2567,6 @@ ${exampleJson}
|
|
|
2610
2567
|
Now, generate the JSON for the requested question.`;
|
|
2611
2568
|
}
|
|
2612
2569
|
async function generateCodingQuestion(clientInput, apiKey) {
|
|
2613
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2614
2570
|
const ai = new genai.GoogleGenAI({ apiKey });
|
|
2615
2571
|
const model = "gemini-2.5-flash";
|
|
2616
2572
|
const config = {
|
|
@@ -2627,7 +2583,7 @@ async function generateCodingQuestion(clientInput, apiKey) {
|
|
|
2627
2583
|
const promptText = buildEnhancedPrompt9(clientInput, attempt);
|
|
2628
2584
|
const promptHash = Buffer.from(promptText).toString("base64").slice(0, 10);
|
|
2629
2585
|
try {
|
|
2630
|
-
DebugLogger.logPrompt(attempt, promptText,
|
|
2586
|
+
DebugLogger.logPrompt(attempt, promptText, { ...clientInput, attemptNumber: attempt, promptHash });
|
|
2631
2587
|
const parts = [{ text: promptText }];
|
|
2632
2588
|
if (clientInput.imageUrl) {
|
|
2633
2589
|
const mimeType = clientInput.imageUrl.endsWith(".png") ? "image/png" : "image/jpeg";
|
|
@@ -2637,7 +2593,7 @@ async function generateCodingQuestion(clientInput, apiKey) {
|
|
|
2637
2593
|
const contents = [{ role: "user", parts }];
|
|
2638
2594
|
const aiResult = await ai.models.generateContent({ model, config, contents });
|
|
2639
2595
|
const response = aiResult;
|
|
2640
|
-
const rawText =
|
|
2596
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
2641
2597
|
const duration = Date.now() - startTime;
|
|
2642
2598
|
DebugLogger.logResponse(attempt, rawText);
|
|
2643
2599
|
if (!rawText) throw new Error("AI returned an empty response.");
|
|
@@ -2669,13 +2625,13 @@ async function generateCodingQuestion(clientInput, apiKey) {
|
|
|
2669
2625
|
solutionCode: aiGeneratedContent.solutionCode,
|
|
2670
2626
|
testCases,
|
|
2671
2627
|
points: 25,
|
|
2672
|
-
topic:
|
|
2628
|
+
topic: clientInput.quizContext?.originalTopic,
|
|
2673
2629
|
difficulty: clientInput.difficulty,
|
|
2674
|
-
contextCode:
|
|
2675
|
-
bloomLevel:
|
|
2676
|
-
learningObjective:
|
|
2677
|
-
subject:
|
|
2678
|
-
category:
|
|
2630
|
+
contextCode: clientInput.quizContext?.plannedContextId,
|
|
2631
|
+
bloomLevel: clientInput.quizContext?.plannedBloomLevel,
|
|
2632
|
+
learningObjective: clientInput.quizContext?.originalLoId,
|
|
2633
|
+
subject: clientInput.quizContext?.originalSubject,
|
|
2634
|
+
category: clientInput.quizContext?.originalCategory,
|
|
2679
2635
|
imageUrl: clientInput.imageUrl
|
|
2680
2636
|
};
|
|
2681
2637
|
const validatedQuestion = CodingQuestionZodSchema.parse(completeQuestion);
|
|
@@ -2697,7 +2653,7 @@ async function generateCodingQuestion(clientInput, apiKey) {
|
|
|
2697
2653
|
}
|
|
2698
2654
|
}
|
|
2699
2655
|
DebugLogger.logAttemptSummary(attemptResults);
|
|
2700
|
-
const errorMessage = `Failed to generate Coding question after ${MAX_RETRY_ATTEMPTS9} attempts. Last error: ${lastError
|
|
2656
|
+
const errorMessage = `Failed to generate Coding question after ${MAX_RETRY_ATTEMPTS9} attempts. Last error: ${lastError?.message}`;
|
|
2701
2657
|
console.error("\n\u274C Final Result: FAILED");
|
|
2702
2658
|
console.error(errorMessage);
|
|
2703
2659
|
return { error: errorMessage };
|
|
@@ -2736,7 +2692,6 @@ var calculateCombinedDifficulty = (plannedQ) => {
|
|
|
2736
2692
|
return "hard";
|
|
2737
2693
|
};
|
|
2738
2694
|
async function generateQuestionsFromQuizPlan(clientInput, apiKey) {
|
|
2739
|
-
var _a, _b;
|
|
2740
2695
|
const { quizPlan, language, imageContexts } = clientInput;
|
|
2741
2696
|
const generatedQuestions = [];
|
|
2742
2697
|
const errors = [];
|
|
@@ -2760,9 +2715,9 @@ async function generateQuestionsFromQuizPlan(clientInput, apiKey) {
|
|
|
2760
2715
|
originalSubject: plannedQ.originalSubject,
|
|
2761
2716
|
originalCategory: plannedQ.originalCategory,
|
|
2762
2717
|
originalTopic: plannedQ.originalTopic,
|
|
2763
|
-
loDescription:
|
|
2718
|
+
loDescription: fullLO?.loDescription || plannedQ.plannedTopic
|
|
2764
2719
|
};
|
|
2765
|
-
const imageUrl = plannedQ.imageId && imageContexts ?
|
|
2720
|
+
const imageUrl = plannedQ.imageId && imageContexts ? imageContexts.find((ctx) => ctx.id === plannedQ.imageId)?.imageUrl : void 0;
|
|
2766
2721
|
const baseClientInput = {
|
|
2767
2722
|
language,
|
|
2768
2723
|
difficulty: calculateCombinedDifficulty(plannedQ),
|
|
@@ -2775,35 +2730,36 @@ async function generateQuestionsFromQuizPlan(clientInput, apiKey) {
|
|
|
2775
2730
|
result = await generateTrueFalseQuestion(baseClientInput, apiKey);
|
|
2776
2731
|
break;
|
|
2777
2732
|
case "multiple_choice":
|
|
2778
|
-
result = await generateMCQQuestion(
|
|
2733
|
+
result = await generateMCQQuestion({ ...baseClientInput, numberOfOptions: 4 }, apiKey);
|
|
2779
2734
|
break;
|
|
2780
2735
|
case "multiple_response":
|
|
2781
|
-
result = await generateMRQQuestion(
|
|
2736
|
+
result = await generateMRQQuestion({ ...baseClientInput, numberOfOptions: 5, minCorrectAnswers: 2, maxCorrectAnswers: 3 }, apiKey);
|
|
2782
2737
|
break;
|
|
2783
2738
|
case "short_answer":
|
|
2784
|
-
result = await generateShortAnswerQuestion(
|
|
2739
|
+
result = await generateShortAnswerQuestion({ ...baseClientInput, isCaseSensitive: false }, apiKey);
|
|
2785
2740
|
break;
|
|
2786
2741
|
case "numeric":
|
|
2787
|
-
result = await generateNumericQuestion(
|
|
2742
|
+
result = await generateNumericQuestion({ ...baseClientInput, allowDecimals: true, tolerance: 0 }, apiKey);
|
|
2788
2743
|
break;
|
|
2789
2744
|
case "fill_in_the_blanks":
|
|
2790
|
-
result = await generateFillInTheBlanksQuestion(
|
|
2745
|
+
result = await generateFillInTheBlanksQuestion({ ...baseClientInput, numberOfBlanks: 2, isCaseSensitive: false }, apiKey);
|
|
2791
2746
|
break;
|
|
2792
2747
|
case "sequence":
|
|
2793
|
-
result = await generateSequenceQuestion(
|
|
2748
|
+
result = await generateSequenceQuestion({ ...baseClientInput, numberOfItems: 4 }, apiKey);
|
|
2794
2749
|
break;
|
|
2795
2750
|
case "matching":
|
|
2796
|
-
result = await generateMatchingQuestion(
|
|
2751
|
+
result = await generateMatchingQuestion({ ...baseClientInput, numberOfPairs: 4, shuffleOptions: true }, apiKey);
|
|
2797
2752
|
break;
|
|
2798
2753
|
case "coding": {
|
|
2799
|
-
const subject =
|
|
2754
|
+
const subject = plannedQ.originalSubject?.toLowerCase() || "";
|
|
2800
2755
|
let codingLanguage = "javascript";
|
|
2801
2756
|
if (subject.includes("swift")) codingLanguage = "swift";
|
|
2802
2757
|
else if (subject.includes("python")) codingLanguage = "python";
|
|
2803
|
-
result = await generateCodingQuestion(
|
|
2758
|
+
result = await generateCodingQuestion({
|
|
2759
|
+
...baseClientInput,
|
|
2804
2760
|
codingLanguage,
|
|
2805
|
-
|
|
2806
|
-
}
|
|
2761
|
+
language: language || "English"
|
|
2762
|
+
}, apiKey);
|
|
2807
2763
|
break;
|
|
2808
2764
|
}
|
|
2809
2765
|
default:
|
|
@@ -3540,19 +3496,17 @@ Now, generate the JSON response.`;
|
|
|
3540
3496
|
console.warn(`Skipping MCQ due to invalid correctTempOptionId: ${correctTempId}`);
|
|
3541
3497
|
continue;
|
|
3542
3498
|
}
|
|
3543
|
-
finalQuestion =
|
|
3499
|
+
finalQuestion = {
|
|
3500
|
+
...rawQuestion,
|
|
3544
3501
|
id: questionId,
|
|
3545
|
-
options: finalOptions.map((
|
|
3546
|
-
var _b = _a, { tempId } = _b, rest = __objRest(_b, ["tempId"]);
|
|
3547
|
-
return rest;
|
|
3548
|
-
}),
|
|
3502
|
+
options: finalOptions.map(({ tempId, ...rest }) => rest),
|
|
3549
3503
|
correctAnswerId: correctFinalOption.id
|
|
3550
|
-
}
|
|
3504
|
+
};
|
|
3551
3505
|
break;
|
|
3552
3506
|
}
|
|
3553
3507
|
case "true_false":
|
|
3554
3508
|
case "short_answer": {
|
|
3555
|
-
finalQuestion =
|
|
3509
|
+
finalQuestion = { ...rawQuestion, id: questionId };
|
|
3556
3510
|
break;
|
|
3557
3511
|
}
|
|
3558
3512
|
default:
|