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