@thanh01.pmt/interactive-quiz-kit 1.0.21 → 1.0.23

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/player.js CHANGED
@@ -1,8 +1,12 @@
1
- import * as React25 from 'react';
2
- import React25__default, { useRef, useState, useImperativeHandle, useCallback, useEffect, forwardRef, useMemo } from 'react';
1
+ import * as React28 from 'react';
2
+ import React28__default, { useRef, useState, useImperativeHandle, useCallback, useEffect, forwardRef, useMemo } from 'react';
3
3
  import ReactDOM from 'react-dom/client';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { z } from 'zod';
6
+ import { genkit } from 'genkit';
7
+ import { gemini20Flash, googleAI } from '@genkit-ai/googleai';
4
8
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
5
- import { Circle, Check, ChevronDown, ChevronUp, CheckCircle, XCircle, RotateCcw, BarChart2, Clock, Percent, AlertTriangle, LogOut, Loader2, AlertCircle, ChevronLeft, ChevronRight } from 'lucide-react';
9
+ import { Circle, Check, ChevronDown, ChevronUp, Loader2, Play, CheckCircle, XCircle, RotateCcw, BarChart2, Clock, Percent, AlertTriangle, LogOut, Wand2, AlertCircle, ChevronLeft, ChevronRight } from 'lucide-react';
6
10
  import { clsx } from 'clsx';
7
11
  import { twMerge } from 'tailwind-merge';
8
12
  import * as LabelPrimitive from '@radix-ui/react-label';
@@ -15,6 +19,11 @@ import rehypeKatex from 'rehype-katex';
15
19
  import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
16
20
  import { Slot } from '@radix-ui/react-slot';
17
21
  import * as SelectPrimitive from '@radix-ui/react-select';
22
+ import CodeMirror from '@uiw/react-codemirror';
23
+ import { cpp } from '@codemirror/lang-cpp';
24
+ import { javascript } from '@codemirror/lang-javascript';
25
+ import { python } from '@codemirror/lang-python';
26
+ import * as TabsPrimitive from '@radix-ui/react-tabs';
18
27
  import * as ProgressPrimitive from '@radix-ui/react-progress';
19
28
  import * as AccordionPrimitive from '@radix-ui/react-accordion';
20
29
  import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
@@ -298,6 +307,847 @@ var SCORMService = class {
298
307
  }
299
308
  };
300
309
 
310
+ // src/services/evaluators/multiple-choice-evaluator.ts
311
+ var MultipleChoiceEvaluator = class {
312
+ async evaluate(question, answer) {
313
+ var _a;
314
+ const points = (_a = question.points) != null ? _a : 0;
315
+ const correctAnswerId = question.correctAnswerId;
316
+ const isCorrect = answer === correctAnswerId;
317
+ const correctOption = question.options.find((opt) => opt.id === correctAnswerId);
318
+ const correctAnswerDetail = {
319
+ id: correctAnswerId,
320
+ value: (correctOption == null ? void 0 : correctOption.text) || ""
321
+ };
322
+ return Promise.resolve({
323
+ isCorrect,
324
+ correctAnswer: correctAnswerDetail,
325
+ pointsEarned: isCorrect ? points : 0
326
+ });
327
+ }
328
+ };
329
+
330
+ // src/services/evaluators/multiple-response-evaluator.ts
331
+ var MultipleResponseEvaluator = class {
332
+ async evaluate(question, answer) {
333
+ var _a;
334
+ const points = (_a = question.points) != null ? _a : 0;
335
+ const correctAnswerIds = question.correctAnswerIds;
336
+ let isCorrect = false;
337
+ if (Array.isArray(answer)) {
338
+ const userAnswerSet = new Set(answer);
339
+ const correctAnswerSet = new Set(correctAnswerIds);
340
+ isCorrect = userAnswerSet.size === correctAnswerSet.size && [...userAnswerSet].every((id) => correctAnswerSet.has(id));
341
+ }
342
+ const correctValues = correctAnswerIds.map(
343
+ (id) => {
344
+ var _a2;
345
+ return ((_a2 = question.options.find((opt) => opt.id === id)) == null ? void 0 : _a2.text) || "";
346
+ }
347
+ );
348
+ const correctAnswerDetail = {
349
+ id: correctAnswerIds,
350
+ value: correctValues
351
+ };
352
+ return Promise.resolve({
353
+ isCorrect,
354
+ correctAnswer: correctAnswerDetail,
355
+ pointsEarned: isCorrect ? points : 0
356
+ });
357
+ }
358
+ };
359
+
360
+ // src/services/evaluators/true-false-evaluator.ts
361
+ var TrueFalseEvaluator = class {
362
+ async evaluate(question, answer) {
363
+ var _a;
364
+ const points = (_a = question.points) != null ? _a : 0;
365
+ const correctAnswer = question.correctAnswer;
366
+ let userAnswer = answer;
367
+ if (typeof answer === "string") {
368
+ userAnswer = answer.toLowerCase() === "true";
369
+ }
370
+ const isCorrect = typeof userAnswer === "boolean" && userAnswer === correctAnswer;
371
+ const correctAnswerDetail = {
372
+ id: null,
373
+ value: correctAnswer
374
+ };
375
+ return Promise.resolve({
376
+ isCorrect,
377
+ correctAnswer: correctAnswerDetail,
378
+ pointsEarned: isCorrect ? points : 0
379
+ });
380
+ }
381
+ };
382
+
383
+ // src/services/evaluators/short-answer-evaluator.ts
384
+ var ShortAnswerEvaluator = class {
385
+ async evaluate(question, answer) {
386
+ var _a, _b;
387
+ const points = (_a = question.points) != null ? _a : 0;
388
+ let isCorrect = false;
389
+ if (typeof answer === "string") {
390
+ const userAnswerTrimmed = answer.trim();
391
+ const caseSensitive = (_b = question.isCaseSensitive) != null ? _b : false;
392
+ isCorrect = question.acceptedAnswers.some(
393
+ (accAns) => caseSensitive ? accAns.trim() === userAnswerTrimmed : accAns.trim().toLowerCase() === userAnswerTrimmed.toLowerCase()
394
+ );
395
+ }
396
+ const correctAnswerDetail = {
397
+ id: null,
398
+ value: question.acceptedAnswers
399
+ };
400
+ return Promise.resolve({
401
+ isCorrect,
402
+ correctAnswer: correctAnswerDetail,
403
+ pointsEarned: isCorrect ? points : 0
404
+ });
405
+ }
406
+ };
407
+
408
+ // src/services/evaluators/numeric-evaluator.ts
409
+ var NumericEvaluator = class {
410
+ async evaluate(question, answer) {
411
+ var _a;
412
+ const points = (_a = question.points) != null ? _a : 0;
413
+ let isCorrect = false;
414
+ if (typeof answer === "string" || typeof answer === "number") {
415
+ const userAnswerNum = parseFloat(String(answer));
416
+ if (!isNaN(userAnswerNum)) {
417
+ isCorrect = question.tolerance != null ? Math.abs(userAnswerNum - question.answer) <= question.tolerance : userAnswerNum === question.answer;
418
+ }
419
+ }
420
+ const correctAnswerDetail = {
421
+ id: null,
422
+ value: question.answer
423
+ };
424
+ return Promise.resolve({
425
+ isCorrect,
426
+ correctAnswer: correctAnswerDetail,
427
+ pointsEarned: isCorrect ? points : 0
428
+ });
429
+ }
430
+ };
431
+
432
+ // src/services/evaluators/sequence-evaluator.ts
433
+ var SequenceEvaluator = class {
434
+ async evaluate(question, answer) {
435
+ var _a;
436
+ const points = (_a = question.points) != null ? _a : 0;
437
+ let isCorrect = false;
438
+ if (Array.isArray(answer) && answer.length === question.correctOrder.length) {
439
+ isCorrect = answer.every((itemId, index) => itemId === question.correctOrder[index]);
440
+ }
441
+ const correctValues = question.correctOrder.map(
442
+ (id) => {
443
+ var _a2;
444
+ return ((_a2 = question.items.find((item) => item.id === id)) == null ? void 0 : _a2.content) || "";
445
+ }
446
+ );
447
+ const correctAnswerDetail = {
448
+ id: question.correctOrder,
449
+ value: correctValues
450
+ };
451
+ return Promise.resolve({
452
+ isCorrect,
453
+ correctAnswer: correctAnswerDetail,
454
+ pointsEarned: isCorrect ? points : 0
455
+ });
456
+ }
457
+ };
458
+
459
+ // src/services/evaluators/matching-evaluator.ts
460
+ var MatchingEvaluator = class {
461
+ async evaluate(question, answer) {
462
+ var _a;
463
+ const points = (_a = question.points) != null ? _a : 0;
464
+ let isCorrect = false;
465
+ if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
466
+ const userAnswerMap = answer;
467
+ isCorrect = question.correctAnswerMap.length === Object.keys(userAnswerMap).length && question.correctAnswerMap.every((map) => userAnswerMap[map.promptId] === map.optionId);
468
+ }
469
+ const correctMap = question.correctAnswerMap.reduce((acc, curr) => {
470
+ var _a2, _b;
471
+ const promptText = ((_a2 = question.prompts.find((p) => p.id === curr.promptId)) == null ? void 0 : _a2.content) || "";
472
+ const optionText = ((_b = question.options.find((o) => o.id === curr.optionId)) == null ? void 0 : _b.content) || "";
473
+ acc[promptText] = optionText;
474
+ return acc;
475
+ }, {});
476
+ const correctAnswerDetail = {
477
+ id: null,
478
+ value: correctMap
479
+ };
480
+ return Promise.resolve({
481
+ isCorrect,
482
+ correctAnswer: correctAnswerDetail,
483
+ pointsEarned: isCorrect ? points : 0
484
+ });
485
+ }
486
+ };
487
+
488
+ // src/services/evaluators/fill-in-the-blanks-evaluator.ts
489
+ var FillInTheBlanksEvaluator = class {
490
+ async evaluate(question, answer) {
491
+ var _a;
492
+ const points = (_a = question.points) != null ? _a : 0;
493
+ let isCorrect = false;
494
+ if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
495
+ const userAnswerMap = answer;
496
+ isCorrect = question.answers.length > 0 && question.answers.every((correctAnsDef) => {
497
+ var _a2, _b;
498
+ const userValForBlank = (_a2 = userAnswerMap[correctAnsDef.blankId]) == null ? void 0 : _a2.trim();
499
+ if (userValForBlank === void 0) return false;
500
+ const caseSensitive = (_b = question.isCaseSensitive) != null ? _b : false;
501
+ return correctAnsDef.acceptedValues.some(
502
+ (accVal) => caseSensitive ? accVal.trim() === userValForBlank : accVal.trim().toLowerCase() === userValForBlank.toLowerCase()
503
+ );
504
+ });
505
+ }
506
+ const correctMap = question.answers.reduce((acc, curr) => {
507
+ acc[curr.blankId] = curr.acceptedValues.join(" | ");
508
+ return acc;
509
+ }, {});
510
+ const correctAnswerDetail = {
511
+ id: null,
512
+ value: correctMap
513
+ };
514
+ return Promise.resolve({
515
+ isCorrect,
516
+ correctAnswer: correctAnswerDetail,
517
+ pointsEarned: isCorrect ? points : 0
518
+ });
519
+ }
520
+ };
521
+
522
+ // src/services/evaluators/drag-and-drop-evaluator.ts
523
+ var DragAndDropEvaluator = class {
524
+ async evaluate(question, answer) {
525
+ var _a;
526
+ const points = (_a = question.points) != null ? _a : 0;
527
+ let isCorrect = false;
528
+ if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
529
+ const userAnswerMap = answer;
530
+ isCorrect = question.answerMap.length === Object.keys(userAnswerMap).length && question.answerMap.every((map) => userAnswerMap[map.draggableId] === map.dropZoneId);
531
+ }
532
+ const correctMap = question.answerMap.reduce((acc, curr) => {
533
+ var _a2, _b;
534
+ const draggableText = ((_a2 = question.draggableItems.find((d) => d.id === curr.draggableId)) == null ? void 0 : _a2.content) || "";
535
+ const dropZoneText = ((_b = question.dropZones.find((z3) => z3.id === curr.dropZoneId)) == null ? void 0 : _b.label) || "";
536
+ acc[draggableText] = dropZoneText;
537
+ return acc;
538
+ }, {});
539
+ const correctAnswerDetail = {
540
+ id: null,
541
+ value: correctMap
542
+ };
543
+ return Promise.resolve({
544
+ isCorrect,
545
+ correctAnswer: correctAnswerDetail,
546
+ pointsEarned: isCorrect ? points : 0
547
+ });
548
+ }
549
+ };
550
+
551
+ // src/services/evaluators/hotspot-evaluator.ts
552
+ var HotspotEvaluator = class {
553
+ async evaluate(question, answer) {
554
+ var _a;
555
+ const points = (_a = question.points) != null ? _a : 0;
556
+ let isCorrect = false;
557
+ if (Array.isArray(answer)) {
558
+ const userAnswerSet = new Set(answer);
559
+ const correctAnswerSet = new Set(question.correctHotspotIds);
560
+ isCorrect = userAnswerSet.size === correctAnswerSet.size && [...userAnswerSet].every((id) => correctAnswerSet.has(id));
561
+ }
562
+ const correctValues = question.correctHotspotIds.map(
563
+ (id) => {
564
+ var _a2;
565
+ return ((_a2 = question.hotspots.find((h) => h.id === id)) == null ? void 0 : _a2.description) || id;
566
+ }
567
+ );
568
+ const correctAnswerDetail = {
569
+ id: question.correctHotspotIds,
570
+ value: correctValues
571
+ };
572
+ return Promise.resolve({
573
+ isCorrect,
574
+ correctAnswer: correctAnswerDetail,
575
+ pointsEarned: isCorrect ? points : 0
576
+ });
577
+ }
578
+ };
579
+
580
+ // src/services/evaluators/programming-evaluator.ts
581
+ var ProgrammingEvaluator = class {
582
+ async evaluate(question, answer) {
583
+ var _a, _b;
584
+ const points = (_a = question.points) != null ? _a : 0;
585
+ let isCorrect = false;
586
+ if (typeof answer === "string" && typeof question.solutionGeneratedCode === "string") {
587
+ if (typeof window !== "undefined" && ((_b = window.Blockly) == null ? void 0 : _b.JavaScript)) {
588
+ const LocalBlockly = window.Blockly;
589
+ let generatedUserCode = "";
590
+ try {
591
+ const tempWorkspace = new LocalBlockly.Workspace();
592
+ const dom = LocalBlockly.Xml.textToDom(answer);
593
+ LocalBlockly.Xml.domToWorkspace(dom, tempWorkspace);
594
+ generatedUserCode = LocalBlockly.JavaScript.workspaceToCode(tempWorkspace) || "";
595
+ const normalize = (code) => code.replace(/\s+/g, " ").trim();
596
+ isCorrect = normalize(generatedUserCode) === normalize(question.solutionGeneratedCode);
597
+ tempWorkspace.dispose();
598
+ } catch (e) {
599
+ console.error(`Error generating code from user's ${question.questionType} XML for evaluation:`, e);
600
+ isCorrect = false;
601
+ }
602
+ } else {
603
+ console.warn(`Blockly library not available for ${question.questionType} evaluation. Skipping code comparison.`);
604
+ isCorrect = false;
605
+ }
606
+ }
607
+ const correctAnswerDetail = {
608
+ id: null,
609
+ value: question.solutionGeneratedCode || ""
610
+ };
611
+ return Promise.resolve({
612
+ isCorrect,
613
+ correctAnswer: correctAnswerDetail,
614
+ pointsEarned: isCorrect ? points : 0
615
+ });
616
+ }
617
+ };
618
+
619
+ // src/utils/jsonUtils.ts
620
+ var JsonRepairEngine = class {
621
+ /**
622
+ * Attempts to repair unterminated strings in JSON.
623
+ * NOTE: This is a heuristic approach and may not be perfect for all cases.
624
+ */
625
+ static repairUnterminatedStrings(jsonStr) {
626
+ let repaired = jsonStr;
627
+ let inString = false;
628
+ let escaped = false;
629
+ let lastQuoteIndex = -1;
630
+ for (let i = 0; i < repaired.length; i++) {
631
+ const char = repaired[i];
632
+ if (escaped) {
633
+ escaped = false;
634
+ continue;
635
+ }
636
+ if (char === "\\") {
637
+ escaped = true;
638
+ continue;
639
+ }
640
+ if (char === '"') {
641
+ inString = !inString;
642
+ if (inString) {
643
+ lastQuoteIndex = i;
644
+ }
645
+ }
646
+ }
647
+ if (inString && lastQuoteIndex !== -1) {
648
+ const beforeUnterminated = repaired.substring(0, lastQuoteIndex + 1);
649
+ const afterUnterminated = repaired.substring(lastQuoteIndex + 1);
650
+ const breakPoints = [",", "}", "]", "\n"];
651
+ let breakIndex = -1;
652
+ for (let i = 0; i < afterUnterminated.length; i++) {
653
+ if (breakPoints.includes(afterUnterminated[i])) {
654
+ breakIndex = i;
655
+ break;
656
+ }
657
+ }
658
+ if (breakIndex !== -1) {
659
+ const stringContent = afterUnterminated.substring(0, breakIndex);
660
+ const remainder = afterUnterminated.substring(breakIndex);
661
+ const escapedContent = stringContent.replace(new RegExp('(?<!\\\\)"', "g"), '\\"');
662
+ repaired = beforeUnterminated + escapedContent + '"' + remainder;
663
+ } else {
664
+ const escapedContent = afterUnterminated.replace(new RegExp('(?<!\\\\)"', "g"), '\\"');
665
+ repaired = beforeUnterminated + escapedContent + '"';
666
+ }
667
+ }
668
+ return repaired;
669
+ }
670
+ // FIX: Replaced unsafe single quote replacement with a stateful parser.
671
+ /**
672
+ * Safely replaces single quotes with double quotes only for keys and string values,
673
+ * ignoring apostrophes inside already double-quoted strings.
674
+ */
675
+ static safelyFixQuotes(jsonStr) {
676
+ let result = "";
677
+ let inDoubleQuoteString = false;
678
+ let escaped = false;
679
+ for (let i = 0; i < jsonStr.length; i++) {
680
+ const char = jsonStr[i];
681
+ if (escaped) {
682
+ result += char;
683
+ escaped = false;
684
+ continue;
685
+ }
686
+ if (char === "\\") {
687
+ escaped = true;
688
+ result += char;
689
+ continue;
690
+ }
691
+ if (char === '"') {
692
+ inDoubleQuoteString = !inDoubleQuoteString;
693
+ }
694
+ if (char === "'" && !inDoubleQuoteString) {
695
+ result += '"';
696
+ } else {
697
+ result += char;
698
+ }
699
+ }
700
+ return result;
701
+ }
702
+ /**
703
+ * Fixes common JSON formatting issues using more robust methods.
704
+ */
705
+ static applyCommonFixes(jsonStr) {
706
+ let fixed = jsonStr;
707
+ fixed = this.safelyFixQuotes(fixed);
708
+ fixed = fixed.replace(/,\s*([}\]])/g, "$1");
709
+ fixed = fixed.replace(/("|}|\d|]|true|false|null)\s*\n\s*(")/g, "$1,\n$2");
710
+ fixed = fixed.replace(/"[\s\S]*?"/g, (match) => {
711
+ const content = match.substring(1, match.length - 1);
712
+ const fixedContent = content.replace(/\n/g, "\\n").replace(/\r/g, "\\r");
713
+ return `"${fixedContent}"`;
714
+ });
715
+ fixed = fixed.replace(/"(true|false|null)"/g, "$1");
716
+ return fixed;
717
+ }
718
+ /**
719
+ * Validates JSON by attempting to parse and providing detailed error info.
720
+ */
721
+ static validateAndGetError(jsonStr) {
722
+ try {
723
+ JSON.parse(jsonStr);
724
+ return { isValid: true };
725
+ } catch (error) {
726
+ const errorMessage = error.message || "";
727
+ const positionMatch = errorMessage.match(/position (\d+)/);
728
+ const position = positionMatch ? parseInt(positionMatch[1], 10) : void 0;
729
+ return {
730
+ isValid: false,
731
+ error: errorMessage,
732
+ position
733
+ };
734
+ }
735
+ }
736
+ /**
737
+ * Main repair function that attempts multiple strategies.
738
+ */
739
+ static repairJson(jsonStr) {
740
+ var _a;
741
+ let current = jsonStr.trim();
742
+ const maxAttempts = 5;
743
+ let lastError = "";
744
+ let lastPosition = -1;
745
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
746
+ const validation = this.validateAndGetError(current);
747
+ if (validation.isValid) {
748
+ return current;
749
+ }
750
+ console.warn(`JSON repair attempt ${attempt + 1}: ${validation.error}`);
751
+ if (validation.error === lastError && validation.position === lastPosition) {
752
+ console.error("Repair attempt stuck on the same error, aborting this strategy.");
753
+ if (validation.position) {
754
+ const truncated = current.substring(0, validation.position);
755
+ const openBraces = (truncated.match(/{/g) || []).length;
756
+ const closeBraces = (truncated.match(/}/g) || []).length;
757
+ const openBrackets = (truncated.match(/\[/g) || []).length;
758
+ const closeBrackets = (truncated.match(/\]/g) || []).length;
759
+ let repaired = truncated.replace(/,\s*$/, "");
760
+ for (let i = 0; i < openBrackets - closeBrackets; i++) repaired += "]";
761
+ for (let i = 0; i < openBraces - closeBraces; i++) repaired += "}";
762
+ current = repaired;
763
+ const finalValidation = this.validateAndGetError(current);
764
+ if (finalValidation.isValid) return current;
765
+ }
766
+ break;
767
+ }
768
+ lastError = validation.error || "";
769
+ lastPosition = validation.position;
770
+ if ((_a = validation.error) == null ? void 0 : _a.includes("Unterminated string")) {
771
+ current = this.repairUnterminatedStrings(current);
772
+ } else {
773
+ current = this.applyCommonFixes(current);
774
+ }
775
+ }
776
+ try {
777
+ let finalAttempt = this.applyCommonFixes(jsonStr.trim());
778
+ finalAttempt = this.repairUnterminatedStrings(finalAttempt);
779
+ JSON.parse(finalAttempt);
780
+ return finalAttempt;
781
+ } catch (e) {
782
+ throw new Error(`Unable to repair JSON after ${maxAttempts} attempts. Last known error: ${lastError}`);
783
+ }
784
+ }
785
+ };
786
+ function extractJsonFromMarkdown(text) {
787
+ if (!text) {
788
+ throw new Error("Input text is empty or null.");
789
+ }
790
+ const trimmedText = text.trim();
791
+ try {
792
+ JSON.parse(trimmedText);
793
+ return trimmedText;
794
+ } catch (e) {
795
+ }
796
+ const markdownPatterns = [
797
+ /```(?:json|JSON)\s*([\s\S]*?)\s*```/,
798
+ // ```json ... ```
799
+ /```\s*({[\s\S]*?}|\[[\s\S]*?\])\s*```/
800
+ // ``` { ... } ``` or ``` [ ... ] ```
801
+ ];
802
+ for (const pattern of markdownPatterns) {
803
+ const match = trimmedText.match(pattern);
804
+ if (match && match[1]) {
805
+ const content = match[1].trim();
806
+ try {
807
+ JSON.parse(content);
808
+ return content;
809
+ } catch (e) {
810
+ console.warn("JSON inside markdown block is invalid, attempting repair...");
811
+ try {
812
+ return JsonRepairEngine.repairJson(content);
813
+ } catch (repairError) {
814
+ console.warn(`Markdown block repair failed: ${repairError.message}. Trying other strategies...`);
815
+ }
816
+ }
817
+ }
818
+ }
819
+ const firstBrace = trimmedText.indexOf("{");
820
+ const firstBracket = trimmedText.indexOf("[");
821
+ let startIndex = -1;
822
+ if (firstBrace === -1 && firstBracket === -1) ; else if (firstBrace === -1) {
823
+ startIndex = firstBracket;
824
+ } else if (firstBracket === -1) {
825
+ startIndex = firstBrace;
826
+ } else {
827
+ startIndex = Math.min(firstBrace, firstBracket);
828
+ }
829
+ if (startIndex !== -1) {
830
+ const textToProcess = trimmedText.substring(startIndex);
831
+ let balance = 0;
832
+ let inString = false;
833
+ let escaped = false;
834
+ const startChar = textToProcess[0];
835
+ const endChar = startChar === "{" ? "}" : "]";
836
+ for (let i = 0; i < textToProcess.length; i++) {
837
+ const char = textToProcess[i];
838
+ if (escaped) {
839
+ escaped = false;
840
+ continue;
841
+ }
842
+ if (char === "\\") {
843
+ escaped = true;
844
+ continue;
845
+ }
846
+ if (char === '"') {
847
+ inString = !inString;
848
+ }
849
+ if (!inString) {
850
+ if (char === startChar) balance++;
851
+ if (char === endChar) balance--;
852
+ }
853
+ if (balance === 0 && i > 0) {
854
+ const potentialJson = textToProcess.substring(0, i + 1);
855
+ try {
856
+ JSON.parse(potentialJson);
857
+ return potentialJson;
858
+ } catch (e) {
859
+ console.warn(`Balanced JSON segment is invalid, attempting repair...`);
860
+ try {
861
+ return JsonRepairEngine.repairJson(potentialJson);
862
+ } catch (repairError) {
863
+ console.warn(`Repair failed for balanced segment: ${repairError.message}`);
864
+ }
865
+ }
866
+ break;
867
+ }
868
+ }
869
+ }
870
+ console.warn("All extraction strategies failed, attempting to repair the entire input text as a last resort.");
871
+ try {
872
+ return JsonRepairEngine.repairJson(trimmedText);
873
+ } catch (finalError) {
874
+ throw new Error(`Unable to extract or repair valid JSON from AI response. Preview: "${trimmedText.substring(0, 100)}...". Final error: ${finalError.message}`);
875
+ }
876
+ }
877
+ z.object({
878
+ language: z.custom(),
879
+ problemPrompt: z.string(),
880
+ userCode: z.string(),
881
+ testCase: z.custom()
882
+ });
883
+ var AIEvaluationOutputSchema = z.object({
884
+ passed: z.boolean().describe("Did the user's code produce the expected output for the given input?"),
885
+ actualOutput: z.any().describe("The actual output produced by the user's code."),
886
+ reasoning: z.string().describe("A brief explanation of why the code passed or failed, or if there was a syntax error.")
887
+ });
888
+ var EvaluateUserCodeOutputSchema = AIEvaluationOutputSchema;
889
+
890
+ // src/ai/flows/evaluate-user-code.ts
891
+ async function evaluateUserCode(clientInput, apiKey) {
892
+ try {
893
+ const ai = genkit({
894
+ plugins: [googleAI({ apiKey })],
895
+ model: gemini20Flash
896
+ });
897
+ const { language, problemPrompt, userCode, testCase } = clientInput;
898
+ const promptText = `
899
+ You are an expert Code Judge and Teaching Assistant for a ${language} programming course.
900
+ Your task is to evaluate a student's code submission for a specific problem against a single test case.
901
+
902
+ ## Problem Description
903
+ ${problemPrompt}
904
+
905
+ ## Student's Code Submission
906
+ \`\`\`${language}
907
+ ${userCode}
908
+ \`\`\`
909
+
910
+ ## Test Case to Evaluate
911
+ - Input(s): ${JSON.stringify(testCase.input)}
912
+ - Expected Output: ${JSON.stringify(testCase.expectedOutput)}
913
+
914
+ ## Your Task
915
+ 1. **Analyze Execution:** Mentally execute the student's code with the provided input(s).
916
+ 2. **Determine Output:** Figure out what the actual output of the code would be.
917
+ 3. **Compare:** Compare the actual output with the expected output.
918
+ 4. **Handle Errors:** If the code has a syntax error or would crash, treat it as a failure.
919
+ 5. **Provide Reasoning:** Briefly explain your conclusion. If it failed, explain why (e.g., "incorrect result", "infinite loop", "syntax error on line 5").
920
+
921
+ **CRITICAL JSON OUTPUT FORMAT:**
922
+ Return ONLY the JSON object with this EXACT structure.
923
+
924
+ \`\`\`json
925
+ {
926
+ "passed": false,
927
+ "actualOutput": 5,
928
+ "reasoning": "The function correctly summed the numbers but did not filter for only even numbers."
929
+ }
930
+ \`\`\`
931
+
932
+ Return only the JSON response.`;
933
+ const response = await ai.generate(promptText);
934
+ const rawText = response.text;
935
+ const jsonText = extractJsonFromMarkdown(rawText);
936
+ const aiGeneratedContent = JSON.parse(jsonText);
937
+ return EvaluateUserCodeOutputSchema.parse(aiGeneratedContent);
938
+ } catch (error) {
939
+ console.error("Error evaluating user code:", error);
940
+ if (error instanceof z.ZodError) {
941
+ throw new Error(`AI evaluation output validation failed: ${error.message}`);
942
+ }
943
+ return {
944
+ passed: false,
945
+ actualOutput: "Evaluation Error",
946
+ reasoning: `The AI judge failed to process the code. Error: ${error.message}`
947
+ };
948
+ }
949
+ }
950
+
951
+ // src/services/APIKeyService.ts
952
+ var GEMINI_API_KEY_SERVICE_NAME = "gemini";
953
+ var LOCAL_STORAGE_PREFIX = "iqk_api_keys_";
954
+ function _encode(data) {
955
+ if (typeof window !== "undefined" && typeof window.btoa === "function") {
956
+ try {
957
+ return window.btoa(data);
958
+ } catch (e) {
959
+ console.error("Base64 encoding (btoa) failed:", e);
960
+ return data;
961
+ }
962
+ }
963
+ return data;
964
+ }
965
+ function _decode(data) {
966
+ if (typeof window !== "undefined" && typeof window.atob === "function") {
967
+ try {
968
+ return window.atob(data);
969
+ } catch (e) {
970
+ console.error("Base64 decoding (atob) failed:", e);
971
+ return data;
972
+ }
973
+ }
974
+ return data;
975
+ }
976
+ var APIKeyService = class {
977
+ static getStorageKey(serviceName) {
978
+ return `${LOCAL_STORAGE_PREFIX}${serviceName}`;
979
+ }
980
+ /**
981
+ * Saves an API key to localStorage. The key is mildly obfuscated using Base64.
982
+ * @param serviceName - The name of the service (e.g., 'gemini').
983
+ * @param apiKey - The API key to save.
984
+ */
985
+ static saveAPIKey(serviceName, apiKey) {
986
+ if (typeof window !== "undefined" && window.localStorage) {
987
+ try {
988
+ const encodedKey = _encode(apiKey);
989
+ localStorage.setItem(this.getStorageKey(serviceName), encodedKey);
990
+ } catch (e) {
991
+ console.error(`Error saving API key for ${serviceName} to localStorage:`, e);
992
+ }
993
+ } else {
994
+ console.warn("localStorage is not available. APIKeyService cannot save keys.");
995
+ }
996
+ }
997
+ /**
998
+ * Retrieves an API key from localStorage.
999
+ * @param serviceName - The name of the service.
1000
+ * @returns The decoded API key, or null if not found or if localStorage is unavailable.
1001
+ */
1002
+ static getAPIKey(serviceName) {
1003
+ if (typeof window !== "undefined" && window.localStorage) {
1004
+ try {
1005
+ const storedKey = localStorage.getItem(this.getStorageKey(serviceName));
1006
+ if (storedKey) {
1007
+ return _decode(storedKey);
1008
+ }
1009
+ } catch (e) {
1010
+ console.error(`Error retrieving API key for ${serviceName} from localStorage:`, e);
1011
+ }
1012
+ }
1013
+ return null;
1014
+ }
1015
+ /**
1016
+ * Removes an API key from localStorage.
1017
+ * @param serviceName - The name of the service.
1018
+ */
1019
+ static removeAPIKey(serviceName) {
1020
+ if (typeof window !== "undefined" && window.localStorage) {
1021
+ try {
1022
+ localStorage.removeItem(this.getStorageKey(serviceName));
1023
+ } catch (e) {
1024
+ console.error(`Error removing API key for ${serviceName} from localStorage:`, e);
1025
+ }
1026
+ }
1027
+ }
1028
+ /**
1029
+ * Checks if an API key exists in localStorage for the given service.
1030
+ * @param serviceName - The name of the service.
1031
+ * @returns True if a key exists, false otherwise.
1032
+ */
1033
+ static hasAPIKey(serviceName) {
1034
+ return this.getAPIKey(serviceName) !== null;
1035
+ }
1036
+ };
1037
+
1038
+ // src/services/CodeEvaluationService.ts
1039
+ var CodeEvaluationService = class {
1040
+ constructor() {
1041
+ this.apiKey = APIKeyService.getAPIKey(GEMINI_API_KEY_SERVICE_NAME);
1042
+ }
1043
+ /**
1044
+ * Evaluates a user's code against a single test case using an AI judge.
1045
+ * @param question The full CodingQuestion object.
1046
+ * @param userCode The user's submitted code string.
1047
+ * @param testCase The specific TestCase to evaluate against.
1048
+ * @returns A promise that resolves to an EvaluationResult object.
1049
+ */
1050
+ async evaluateSingleTestCase(question, userCode, testCase) {
1051
+ if (!this.apiKey) {
1052
+ return {
1053
+ testCaseId: testCase.id,
1054
+ passed: false,
1055
+ actualOutput: "Configuration Error",
1056
+ reasoning: "API Key is not configured."
1057
+ };
1058
+ }
1059
+ const aiResult = await evaluateUserCode({
1060
+ language: question.language,
1061
+ problemPrompt: question.prompt,
1062
+ userCode,
1063
+ testCase
1064
+ }, this.apiKey);
1065
+ return __spreadValues({
1066
+ testCaseId: testCase.id
1067
+ }, aiResult);
1068
+ }
1069
+ /**
1070
+ * Evaluates user's code against all test cases for a given question.
1071
+ * @param question The full CodingQuestion object.
1072
+ * @param userCode The user's submitted code string.
1073
+ * @returns A promise that resolves to an array of EvaluationResult objects.
1074
+ */
1075
+ async evaluateAllTestCases(question, userCode) {
1076
+ const results = [];
1077
+ for (const testCase of question.testCases) {
1078
+ const result = await this.evaluateSingleTestCase(question, userCode, testCase);
1079
+ results.push(result);
1080
+ }
1081
+ return results;
1082
+ }
1083
+ /**
1084
+ * Evaluates user's code against only the public test cases for a given question.
1085
+ * Useful for a "Run Tests" button before final submission.
1086
+ * @param question The full CodingQuestion object.
1087
+ * @param userCode The user's submitted code string.
1088
+ * @returns A promise that resolves to an array of EvaluationResult objects.
1089
+ */
1090
+ async evaluatePublicTestCases(question, userCode) {
1091
+ const publicTestCases = question.testCases.filter((tc) => tc.isPublic);
1092
+ const results = [];
1093
+ for (const testCase of publicTestCases) {
1094
+ const result = await this.evaluateSingleTestCase(question, userCode, testCase);
1095
+ results.push(result);
1096
+ }
1097
+ return results;
1098
+ }
1099
+ };
1100
+
1101
+ // src/services/evaluators/coding-evaluator.ts
1102
+ var CodingEvaluator = class {
1103
+ async evaluate(question, answer) {
1104
+ var _a;
1105
+ const points = (_a = question.points) != null ? _a : 0;
1106
+ if (typeof answer !== "string" || !answer.trim()) {
1107
+ return {
1108
+ isCorrect: false,
1109
+ correctAnswer: { id: null, value: question.solutionCode },
1110
+ pointsEarned: 0,
1111
+ evaluationDetails: question.testCases.map((tc) => ({
1112
+ testCaseId: tc.id,
1113
+ passed: false,
1114
+ actualOutput: "No submission",
1115
+ reasoning: "User did not submit any code."
1116
+ }))
1117
+ };
1118
+ }
1119
+ try {
1120
+ const evaluationService = new CodeEvaluationService();
1121
+ const testCaseResults = await evaluationService.evaluateAllTestCases(question, answer);
1122
+ const isCorrect = testCaseResults.every((result) => result.passed);
1123
+ const correctAnswerDetail = {
1124
+ id: null,
1125
+ value: question.solutionCode
1126
+ };
1127
+ return {
1128
+ isCorrect,
1129
+ correctAnswer: correctAnswerDetail,
1130
+ pointsEarned: isCorrect ? points : 0,
1131
+ evaluationDetails: testCaseResults
1132
+ // Pass through the detailed results
1133
+ };
1134
+ } catch (error) {
1135
+ console.error("A critical error occurred during code evaluation:", error);
1136
+ return {
1137
+ isCorrect: false,
1138
+ correctAnswer: { id: null, value: question.solutionCode },
1139
+ pointsEarned: 0,
1140
+ evaluationDetails: question.testCases.map((tc) => ({
1141
+ testCaseId: tc.id,
1142
+ passed: false,
1143
+ actualOutput: "Evaluation Error",
1144
+ reasoning: error instanceof Error ? error.message : "An unknown error occurred."
1145
+ }))
1146
+ };
1147
+ }
1148
+ }
1149
+ };
1150
+
301
1151
  // src/services/QuizEngine.ts
302
1152
  var QuizEngine = class {
303
1153
  constructor(options) {
@@ -314,6 +1164,8 @@ var QuizEngine = class {
314
1164
  this.callbacks = options.callbacks || {};
315
1165
  this.questions = ((_a = this.config.settings) == null ? void 0 : _a.shuffleQuestions) ? [...this.config.questions].sort(() => Math.random() - 0.5) : this.config.questions;
316
1166
  this.overallStartTime = Date.now();
1167
+ this.evaluators = /* @__PURE__ */ new Map();
1168
+ this.registerEvaluators();
317
1169
  if (((_b = this.config.settings) == null ? void 0 : _b.timeLimitMinutes) && this.config.settings.timeLimitMinutes > 0) {
318
1170
  this.timeLeftInSeconds = this.config.settings.timeLimitMinutes * 60;
319
1171
  }
@@ -352,6 +1204,22 @@ var QuizEngine = class {
352
1204
  }
353
1205
  (_e = (_d = this.callbacks).onQuestionChange) == null ? void 0 : _e.call(_d, initialQ, this.getCurrentQuestionNumber(), this.getTotalQuestions());
354
1206
  }
1207
+ registerEvaluators() {
1208
+ this.evaluators.set("multiple_choice", new MultipleChoiceEvaluator());
1209
+ this.evaluators.set("multiple_response", new MultipleResponseEvaluator());
1210
+ this.evaluators.set("true_false", new TrueFalseEvaluator());
1211
+ this.evaluators.set("short_answer", new ShortAnswerEvaluator());
1212
+ this.evaluators.set("numeric", new NumericEvaluator());
1213
+ this.evaluators.set("sequence", new SequenceEvaluator());
1214
+ this.evaluators.set("matching", new MatchingEvaluator());
1215
+ this.evaluators.set("fill_in_the_blanks", new FillInTheBlanksEvaluator());
1216
+ this.evaluators.set("drag_and_drop", new DragAndDropEvaluator());
1217
+ this.evaluators.set("hotspot", new HotspotEvaluator());
1218
+ const programmingEvaluator = new ProgrammingEvaluator();
1219
+ this.evaluators.set("blockly_programming", programmingEvaluator);
1220
+ this.evaluators.set("scratch_programming", programmingEvaluator);
1221
+ this.evaluators.set("coding", new CodingEvaluator());
1222
+ }
355
1223
  _recordCurrentQuestionTime() {
356
1224
  if (this.questionStartTime && this.currentQuestionIndex >= 0 && this.currentQuestionIndex < this.questions.length) {
357
1225
  const currentQId = this.questions[this.currentQuestionIndex].id;
@@ -444,180 +1312,28 @@ var QuizEngine = class {
444
1312
  }
445
1313
  return this.getCurrentQuestion();
446
1314
  }
447
- evaluateQuestion(question, answer) {
448
- var _a, _b, _c;
449
- let isCorrect = false;
450
- let correctAnswerDetail = null;
451
- const points = (_a = question.points) != null ? _a : 0;
452
- const findOptionText = (q, id) => {
453
- var _a2;
454
- return ((_a2 = q.options.find((opt) => opt.id === id)) == null ? void 0 : _a2.text) || "";
455
- };
456
- switch (question.questionType) {
457
- case "multiple_choice": {
458
- const q = question;
459
- const correctAnswerId = q.correctAnswerId;
460
- const correctValue = findOptionText(q, correctAnswerId);
461
- correctAnswerDetail = { id: correctAnswerId, value: correctValue };
462
- isCorrect = answer === correctAnswerId;
463
- break;
464
- }
465
- case "multiple_response": {
466
- const q = question;
467
- const correctAnswerIds = q.correctAnswerIds;
468
- const correctValues = correctAnswerIds.map((id) => findOptionText(q, id));
469
- correctAnswerDetail = { id: correctAnswerIds, value: correctValues };
470
- if (Array.isArray(answer)) {
471
- const userAnswerSet = new Set(answer);
472
- const correctAnswerSet = new Set(correctAnswerIds);
473
- isCorrect = userAnswerSet.size === correctAnswerSet.size && [...userAnswerSet].every((id) => correctAnswerSet.has(id));
474
- }
475
- break;
476
- }
477
- case "true_false": {
478
- const q = question;
479
- correctAnswerDetail = { id: null, value: q.correctAnswer };
480
- let tfAnswer = answer;
481
- if (typeof answer === "string") tfAnswer = answer.toLowerCase() === "true";
482
- isCorrect = typeof tfAnswer === "boolean" && tfAnswer === q.correctAnswer;
483
- break;
484
- }
485
- case "short_answer": {
486
- const q = question;
487
- correctAnswerDetail = { id: null, value: q.acceptedAnswers };
488
- if (typeof answer === "string") {
489
- const userAnswerTrimmed = answer.trim();
490
- const caseSensitive = (_b = q.isCaseSensitive) != null ? _b : false;
491
- isCorrect = q.acceptedAnswers.some((accAns) => caseSensitive ? accAns.trim() === userAnswerTrimmed : accAns.trim().toLowerCase() === userAnswerTrimmed.toLowerCase());
492
- }
493
- break;
494
- }
495
- case "numeric": {
496
- const q = question;
497
- correctAnswerDetail = { id: null, value: q.answer };
498
- if (typeof answer === "string" || typeof answer === "number") {
499
- const userAnswerNum = parseFloat(String(answer));
500
- if (!isNaN(userAnswerNum)) {
501
- isCorrect = q.tolerance != null ? Math.abs(userAnswerNum - q.answer) <= q.tolerance : userAnswerNum === q.answer;
502
- }
503
- }
504
- break;
505
- }
506
- case "sequence": {
507
- const q = question;
508
- const correctValues = q.correctOrder.map((id) => {
509
- var _a2;
510
- return ((_a2 = q.items.find((item) => item.id === id)) == null ? void 0 : _a2.content) || "";
511
- });
512
- correctAnswerDetail = { id: q.correctOrder, value: correctValues };
513
- if (Array.isArray(answer) && answer.length === q.correctOrder.length) {
514
- isCorrect = answer.every((itemId, index) => itemId === q.correctOrder[index]);
515
- }
516
- break;
517
- }
518
- case "matching": {
519
- const q = question;
520
- const correctMap = q.correctAnswerMap.reduce((acc, curr) => {
521
- var _a2, _b2;
522
- const promptText = ((_a2 = q.prompts.find((p) => p.id === curr.promptId)) == null ? void 0 : _a2.content) || "";
523
- const optionText = ((_b2 = q.options.find((o) => o.id === curr.optionId)) == null ? void 0 : _b2.content) || "";
524
- acc[promptText] = optionText;
525
- return acc;
526
- }, {});
527
- correctAnswerDetail = { id: null, value: correctMap };
528
- if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
529
- const userAnswerMap = answer;
530
- isCorrect = q.correctAnswerMap.length === Object.keys(userAnswerMap).length && q.correctAnswerMap.every((map) => userAnswerMap[map.promptId] === map.optionId);
531
- }
532
- break;
533
- }
534
- case "fill_in_the_blanks": {
535
- const q = question;
536
- const correctMap = q.answers.reduce((acc, curr) => {
537
- acc[curr.blankId] = curr.acceptedValues.join(" | ");
538
- return acc;
539
- }, {});
540
- correctAnswerDetail = { id: null, value: correctMap };
541
- if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
542
- const userAnswerMap = answer;
543
- isCorrect = q.answers.every((correctAnsDef) => {
544
- var _a2, _b2;
545
- const userValForBlank = (_a2 = userAnswerMap[correctAnsDef.blankId]) == null ? void 0 : _a2.trim();
546
- if (userValForBlank === void 0) return false;
547
- const caseSensitive = (_b2 = q.isCaseSensitive) != null ? _b2 : false;
548
- return correctAnsDef.acceptedValues.some((accVal) => caseSensitive ? accVal.trim() === userValForBlank : accVal.trim().toLowerCase() === userValForBlank.toLowerCase());
549
- });
550
- }
551
- break;
552
- }
553
- case "drag_and_drop": {
554
- const q = question;
555
- const correctMap = q.answerMap.reduce((acc, curr) => {
556
- var _a2, _b2;
557
- const draggableText = ((_a2 = q.draggableItems.find((d) => d.id === curr.draggableId)) == null ? void 0 : _a2.content) || "";
558
- const dropZoneText = ((_b2 = q.dropZones.find((z) => z.id === curr.dropZoneId)) == null ? void 0 : _b2.label) || "";
559
- acc[draggableText] = dropZoneText;
560
- return acc;
561
- }, {});
562
- correctAnswerDetail = { id: null, value: correctMap };
563
- if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
564
- const userAnswerMap = answer;
565
- isCorrect = q.answerMap.length === Object.keys(userAnswerMap).length && q.answerMap.every((map) => userAnswerMap[map.draggableId] === map.dropZoneId);
566
- }
567
- break;
568
- }
569
- case "hotspot": {
570
- const q = question;
571
- const correctValues = q.correctHotspotIds.map((id) => {
572
- var _a2;
573
- return ((_a2 = q.hotspots.find((h) => h.id === id)) == null ? void 0 : _a2.description) || id;
574
- });
575
- correctAnswerDetail = { id: q.correctHotspotIds, value: correctValues };
576
- if (Array.isArray(answer)) {
577
- const userAnswerSet = new Set(answer);
578
- const correctAnswerSet = new Set(q.correctHotspotIds);
579
- isCorrect = userAnswerSet.size === correctAnswerSet.size && [...userAnswerSet].every((id) => correctAnswerSet.has(id));
580
- }
581
- break;
582
- }
583
- case "blockly_programming":
584
- case "scratch_programming": {
585
- const q = question;
586
- correctAnswerDetail = { id: null, value: q.solutionGeneratedCode || "" };
587
- if (typeof answer === "string" && typeof q.solutionGeneratedCode === "string") {
588
- if (typeof window !== "undefined" && ((_c = window.Blockly) == null ? void 0 : _c.JavaScript)) {
589
- const LocalBlockly = window.Blockly;
590
- let generatedUserCode = "";
591
- try {
592
- const tempWorkspace = new LocalBlockly.Workspace();
593
- const dom = LocalBlockly.Xml.textToDom(answer);
594
- LocalBlockly.Xml.domToWorkspace(dom, tempWorkspace);
595
- generatedUserCode = LocalBlockly.JavaScript.workspaceToCode(tempWorkspace) || "";
596
- const normalize = (code) => code.replace(/\s+/g, " ").trim();
597
- isCorrect = normalize(generatedUserCode) === normalize(q.solutionGeneratedCode);
598
- tempWorkspace.dispose();
599
- } catch (e) {
600
- console.error(`Error generating code from user's ${q.questionType} XML for evaluation:`, e);
601
- isCorrect = false;
602
- }
603
- } else {
604
- console.warn(`Blockly library not available in QuizEngine for ${q.questionType} code generation during evaluation. Skipping code comparison.`);
605
- isCorrect = false;
606
- }
1315
+ getElapsedTime() {
1316
+ return Date.now() - this.overallStartTime;
1317
+ }
1318
+ destroy() {
1319
+ this.stopTimer();
1320
+ this._recordCurrentQuestionTime();
1321
+ if (this.scormService && this.scormService.hasAPI()) {
1322
+ if (["initialized", "committed", "sending_data"].includes(this.quizResultState.scormStatus || "")) {
1323
+ const termResult = this.scormService.terminate();
1324
+ if (termResult.success) {
1325
+ this.quizResultState.scormStatus = "terminated";
1326
+ } else {
1327
+ this.quizResultState.scormStatus = "error";
1328
+ this.quizResultState.scormError = termResult.error || "SCORM termination failed on destroy.";
607
1329
  }
608
- break;
609
- }
610
- default: {
611
- const _exhaustiveCheck = question;
612
- console.warn("Unsupported question type in QuizEngine evaluation:", _exhaustiveCheck);
613
- isCorrect = false;
614
- correctAnswerDetail = { id: null, value: "Evaluation not implemented." };
615
1330
  }
616
1331
  }
617
- return { isCorrect, correctAnswer: correctAnswerDetail, pointsEarned: isCorrect ? points : 0 };
1332
+ this.scormService = null;
618
1333
  }
1334
+ // (Tiếp theo từ Phần 1)
619
1335
  async calculateResults() {
620
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
1336
+ var _a, _b, _c, _d, _e;
621
1337
  this.stopTimer();
622
1338
  this._recordCurrentQuestionTime();
623
1339
  let totalScore = 0;
@@ -627,92 +1343,31 @@ var QuizEngine = class {
627
1343
  for (const question of this.questions) {
628
1344
  const userAnswerRaw = this.userAnswers.get(question.id) || null;
629
1345
  maxScore += (_a = question.points) != null ? _a : 0;
630
- const { isCorrect, correctAnswer: correctAnswerDetail, pointsEarned } = this.evaluateQuestion(question, userAnswerRaw);
1346
+ const evaluator = this.evaluators.get(question.questionType);
1347
+ if (!evaluator) {
1348
+ console.warn(`No evaluator found for question type: ${question.questionType}`);
1349
+ questionResultsArray.push({
1350
+ questionId: question.id,
1351
+ questionType: question.questionType,
1352
+ prompt: question.prompt,
1353
+ isCorrect: false,
1354
+ pointsEarned: 0,
1355
+ userAnswer: { id: null, value: userAnswerRaw },
1356
+ correctAnswer: { id: null, value: "Evaluation not implemented." },
1357
+ timeSpentSeconds: parseFloat((this.questionTimings.get(question.id) || 0).toFixed(2))
1358
+ });
1359
+ continue;
1360
+ }
1361
+ const {
1362
+ isCorrect,
1363
+ correctAnswer: correctAnswerDetail,
1364
+ pointsEarned,
1365
+ evaluationDetails
1366
+ } = await evaluator.evaluate(question, userAnswerRaw);
631
1367
  totalScore += pointsEarned;
632
1368
  const timeSpentOnThisQuestion = parseFloat((this.questionTimings.get(question.id) || 0).toFixed(2));
633
- accumulatedTotalTimeSpent += timeSpentOnThisQuestion;
634
- let userAnswerDetail = null;
635
- let allOptions = void 0;
636
- if (userAnswerRaw !== null) {
637
- switch (question.questionType) {
638
- case "multiple_choice": {
639
- const q = question;
640
- allOptions = q.options.map((opt) => ({ id: opt.id, value: opt.text }));
641
- const id = userAnswerRaw;
642
- userAnswerDetail = { id, value: ((_b = allOptions.find((opt) => opt.id === id)) == null ? void 0 : _b.value) || "" };
643
- break;
644
- }
645
- case "multiple_response": {
646
- const q = question;
647
- allOptions = q.options.map((opt) => ({ id: opt.id, value: opt.text }));
648
- const ids = userAnswerRaw;
649
- const values = ids.map((id) => {
650
- var _a2;
651
- return ((_a2 = allOptions == null ? void 0 : allOptions.find((opt) => opt.id === id)) == null ? void 0 : _a2.value) || "";
652
- });
653
- userAnswerDetail = { id: ids, value: values };
654
- break;
655
- }
656
- case "true_false":
657
- case "short_answer":
658
- case "numeric":
659
- userAnswerDetail = { id: null, value: userAnswerRaw };
660
- break;
661
- case "sequence": {
662
- const q = question;
663
- allOptions = q.items.map((item) => ({ id: item.id, value: item.content }));
664
- const ids = userAnswerRaw;
665
- const values = ids.map((id) => {
666
- var _a2;
667
- return ((_a2 = allOptions == null ? void 0 : allOptions.find((opt) => opt.id === id)) == null ? void 0 : _a2.value) || "";
668
- });
669
- userAnswerDetail = { id: ids, value: values };
670
- break;
671
- }
672
- case "matching": {
673
- const q = question;
674
- const userAnswerMap = userAnswerRaw;
675
- const valueMap = {};
676
- for (const promptId in userAnswerMap) {
677
- const optionId = userAnswerMap[promptId];
678
- const promptText = ((_c = q.prompts.find((p) => p.id === promptId)) == null ? void 0 : _c.content) || "";
679
- const optionText = ((_d = q.options.find((o) => o.id === optionId)) == null ? void 0 : _d.content) || "";
680
- valueMap[promptText] = optionText;
681
- }
682
- userAnswerDetail = { id: null, value: valueMap };
683
- break;
684
- }
685
- // --- LOGIC MỚI ĐƯỢC THÊM VÀO ---
686
- case "fill_in_the_blanks": {
687
- if (typeof userAnswerRaw === "object" && userAnswerRaw !== null && !Array.isArray(userAnswerRaw)) {
688
- userAnswerDetail = { id: null, value: userAnswerRaw };
689
- }
690
- break;
691
- }
692
- case "drag_and_drop": {
693
- const q = question;
694
- if (typeof userAnswerRaw === "object" && userAnswerRaw !== null && !Array.isArray(userAnswerRaw)) {
695
- const userAnswerMapByIds = userAnswerRaw;
696
- const enrichedUserAnswerMap = {};
697
- for (const draggableId in userAnswerMapByIds) {
698
- const dropZoneId = userAnswerMapByIds[draggableId];
699
- const draggableText = ((_e = q.draggableItems.find((d) => d.id === draggableId)) == null ? void 0 : _e.content) || `(ID: ${draggableId})`;
700
- const dropZoneText = ((_f = q.dropZones.find((z) => z.id === dropZoneId)) == null ? void 0 : _f.label) || `(ID: ${dropZoneId})`;
701
- enrichedUserAnswerMap[draggableText] = dropZoneText;
702
- }
703
- userAnswerDetail = { id: null, value: enrichedUserAnswerMap };
704
- }
705
- break;
706
- }
707
- // ------------------------------------
708
- // Các loại câu hỏi còn lại vẫn giữ fallback
709
- case "hotspot":
710
- case "blockly_programming":
711
- case "scratch_programming":
712
- userAnswerDetail = { id: null, value: userAnswerRaw };
713
- break;
714
- }
715
- }
1369
+ accumulatedTotalTimeSpent += timeSpentOnThisQuestion;
1370
+ const userAnswerDetail = this.formatUserAnswerDetail(question, userAnswerRaw);
716
1371
  questionResultsArray.push({
717
1372
  questionId: question.id,
718
1373
  questionType: question.questionType,
@@ -721,18 +1376,18 @@ var QuizEngine = class {
721
1376
  pointsEarned,
722
1377
  userAnswer: userAnswerDetail,
723
1378
  correctAnswer: correctAnswerDetail,
724
- allOptions,
725
- timeSpentSeconds: timeSpentOnThisQuestion
1379
+ timeSpentSeconds: timeSpentOnThisQuestion,
1380
+ evaluationDetails
726
1381
  });
727
1382
  }
728
1383
  const percentage = maxScore > 0 ? parseFloat((totalScore / maxScore * 100).toFixed(2)) : 0;
729
1384
  let passed = void 0;
730
- if (((_g = this.config.settings) == null ? void 0 : _g.passingScorePercent) != null) {
1385
+ if (((_b = this.config.settings) == null ? void 0 : _b.passingScorePercent) != null) {
731
1386
  passed = percentage >= this.config.settings.passingScorePercent;
732
1387
  }
733
1388
  const totalQuizTimeSpentSeconds = parseFloat(accumulatedTotalTimeSpent.toFixed(2));
734
1389
  const averageTimePerQuestionSeconds = this.questions.length > 0 ? parseFloat((totalQuizTimeSpentSeconds / this.questions.length).toFixed(2)) : 0;
735
- const metadataPerformance = this._calculateMetadataPerformance();
1390
+ const metadataPerformance = await this._calculateMetadataPerformance();
736
1391
  const finalResults = __spreadValues({
737
1392
  score: totalScore,
738
1393
  maxScore,
@@ -748,11 +1403,118 @@ var QuizEngine = class {
748
1403
  averageTimePerQuestionSeconds
749
1404
  }, metadataPerformance);
750
1405
  this.quizResultState = __spreadValues(__spreadValues({}, this.quizResultState), finalResults);
751
- if ((_h = this.config.settings) == null ? void 0 : _h.scorm) this._sendResultsToSCORM(finalResults);
1406
+ if ((_c = this.config.settings) == null ? void 0 : _c.scorm) this._sendResultsToSCORM(finalResults);
752
1407
  await this._sendResultsToWebhook(finalResults);
753
- (_j = (_i = this.callbacks).onQuizFinish) == null ? void 0 : _j.call(_i, finalResults);
1408
+ (_e = (_d = this.callbacks).onQuizFinish) == null ? void 0 : _e.call(_d, finalResults);
754
1409
  return finalResults;
755
1410
  }
1411
+ formatUserAnswerDetail(question, userAnswerRaw) {
1412
+ var _a, _b, _c, _d, _e;
1413
+ if (userAnswerRaw === null) return null;
1414
+ switch (question.questionType) {
1415
+ case "multiple_choice": {
1416
+ const q = question;
1417
+ const id = userAnswerRaw;
1418
+ return { id, value: ((_a = q.options.find((opt) => opt.id === id)) == null ? void 0 : _a.text) || "" };
1419
+ }
1420
+ case "multiple_response": {
1421
+ const q = question;
1422
+ const ids = userAnswerRaw;
1423
+ const values = ids.map((id) => {
1424
+ var _a2;
1425
+ return ((_a2 = q.options.find((opt) => opt.id === id)) == null ? void 0 : _a2.text) || "";
1426
+ });
1427
+ return { id: ids, value: values };
1428
+ }
1429
+ case "sequence": {
1430
+ const q = question;
1431
+ const ids = userAnswerRaw;
1432
+ const values = ids.map((id) => {
1433
+ var _a2;
1434
+ return ((_a2 = q.items.find((item) => item.id === id)) == null ? void 0 : _a2.content) || "";
1435
+ });
1436
+ return { id: ids, value: values };
1437
+ }
1438
+ case "matching": {
1439
+ const q = question;
1440
+ const userAnswerMap = userAnswerRaw;
1441
+ const valueMap = {};
1442
+ for (const promptId in userAnswerMap) {
1443
+ const optionId = userAnswerMap[promptId];
1444
+ const promptText = ((_b = q.prompts.find((p) => p.id === promptId)) == null ? void 0 : _b.content) || "";
1445
+ const optionText = ((_c = q.options.find((o) => o.id === optionId)) == null ? void 0 : _c.content) || "";
1446
+ valueMap[promptText] = optionText;
1447
+ }
1448
+ return { id: null, value: valueMap };
1449
+ }
1450
+ case "drag_and_drop": {
1451
+ const q = question;
1452
+ if (typeof userAnswerRaw === "object" && userAnswerRaw !== null && !Array.isArray(userAnswerRaw)) {
1453
+ const userAnswerMapByIds = userAnswerRaw;
1454
+ const enrichedUserAnswerMap = {};
1455
+ for (const draggableId in userAnswerMapByIds) {
1456
+ const dropZoneId = userAnswerMapByIds[draggableId];
1457
+ const draggableText = ((_d = q.draggableItems.find((d) => d.id === draggableId)) == null ? void 0 : _d.content) || `(ID: ${draggableId})`;
1458
+ const dropZoneText = ((_e = q.dropZones.find((z3) => z3.id === dropZoneId)) == null ? void 0 : _e.label) || `(ID: ${dropZoneId})`;
1459
+ enrichedUserAnswerMap[draggableText] = dropZoneText;
1460
+ }
1461
+ return { id: null, value: enrichedUserAnswerMap };
1462
+ }
1463
+ return { id: null, value: userAnswerRaw };
1464
+ }
1465
+ default:
1466
+ return { id: null, value: userAnswerRaw };
1467
+ }
1468
+ }
1469
+ async _calculateMetadataPerformance() {
1470
+ var _a;
1471
+ const loPerformanceMap = /* @__PURE__ */ new Map();
1472
+ const categoryPerformanceMap = /* @__PURE__ */ new Map();
1473
+ const topicPerformanceMap = /* @__PURE__ */ new Map();
1474
+ const difficultyPerformanceMap = /* @__PURE__ */ new Map();
1475
+ const bloomLevelPerformanceMap = /* @__PURE__ */ new Map();
1476
+ const updateMap = (map, key, points, isCorrect) => {
1477
+ if (!key) return;
1478
+ const current = map.get(key) || { totalQuestions: 0, correctQuestions: 0, pointsEarned: 0, maxPoints: 0 };
1479
+ current.totalQuestions++;
1480
+ current.maxPoints += points;
1481
+ if (isCorrect) {
1482
+ current.correctQuestions++;
1483
+ current.pointsEarned += points;
1484
+ }
1485
+ map.set(key, current);
1486
+ };
1487
+ for (const q of this.questions) {
1488
+ const userAnswer = this.userAnswers.get(q.id) || null;
1489
+ const evaluator = this.evaluators.get(q.questionType);
1490
+ if (evaluator) {
1491
+ const { isCorrect } = await evaluator.evaluate(q, userAnswer);
1492
+ const pointsForThisQuestion = (_a = q.points) != null ? _a : 0;
1493
+ updateMap(loPerformanceMap, q.learningObjective, pointsForThisQuestion, isCorrect);
1494
+ updateMap(categoryPerformanceMap, q.category, pointsForThisQuestion, isCorrect);
1495
+ updateMap(topicPerformanceMap, q.topic, pointsForThisQuestion, isCorrect);
1496
+ updateMap(difficultyPerformanceMap, q.difficulty, pointsForThisQuestion, isCorrect);
1497
+ updateMap(bloomLevelPerformanceMap, q.bloomLevel, pointsForThisQuestion, isCorrect);
1498
+ }
1499
+ }
1500
+ const formatPerformanceArray = (map, keyName) => {
1501
+ return Array.from(map.entries()).map(([key, data]) => ({
1502
+ [keyName]: key,
1503
+ totalQuestions: data.totalQuestions,
1504
+ correctQuestions: data.correctQuestions,
1505
+ pointsEarned: data.pointsEarned,
1506
+ maxPoints: data.maxPoints,
1507
+ percentage: data.maxPoints > 0 ? parseFloat((data.pointsEarned / data.maxPoints * 100).toFixed(2)) : 0
1508
+ }));
1509
+ };
1510
+ return {
1511
+ performanceByLearningObjective: formatPerformanceArray(loPerformanceMap, "learningObjective"),
1512
+ performanceByCategory: formatPerformanceArray(categoryPerformanceMap, "category"),
1513
+ performanceByTopic: formatPerformanceArray(topicPerformanceMap, "topic"),
1514
+ performanceByDifficulty: formatPerformanceArray(difficultyPerformanceMap, "difficulty"),
1515
+ performanceByBloomLevel: formatPerformanceArray(bloomLevelPerformanceMap, "bloomLevel")
1516
+ };
1517
+ }
756
1518
  async _sendResultsToWebhook(results) {
757
1519
  var _a;
758
1520
  if (!((_a = this.config.settings) == null ? void 0 : _a.webhookUrl)) {
@@ -820,80 +1582,15 @@ var QuizEngine = class {
820
1582
  results.scormError = e instanceof Error ? e.message : "Unknown SCORM data sending error.";
821
1583
  }
822
1584
  }
823
- _calculateMetadataPerformance() {
824
- const loPerformanceMap = /* @__PURE__ */ new Map();
825
- const categoryPerformanceMap = /* @__PURE__ */ new Map();
826
- const topicPerformanceMap = /* @__PURE__ */ new Map();
827
- const difficultyPerformanceMap = /* @__PURE__ */ new Map();
828
- const bloomLevelPerformanceMap = /* @__PURE__ */ new Map();
829
- const updateMap = (map, key, points, isCorrect) => {
830
- if (!key) return;
831
- const current = map.get(key) || { totalQuestions: 0, correctQuestions: 0, pointsEarned: 0, maxPoints: 0 };
832
- current.totalQuestions++;
833
- current.maxPoints += points;
834
- if (isCorrect) {
835
- current.correctQuestions++;
836
- current.pointsEarned += points;
837
- }
838
- map.set(key, current);
839
- };
840
- this.questions.forEach((q) => {
841
- var _a;
842
- const qResult = this.userAnswers.get(q.id);
843
- const { isCorrect } = this.evaluateQuestion(q, qResult || null);
844
- const pointsForThisQuestion = (_a = q.points) != null ? _a : 0;
845
- updateMap(loPerformanceMap, q.learningObjective, pointsForThisQuestion, isCorrect);
846
- updateMap(categoryPerformanceMap, q.category, pointsForThisQuestion, isCorrect);
847
- updateMap(topicPerformanceMap, q.topic, pointsForThisQuestion, isCorrect);
848
- updateMap(difficultyPerformanceMap, q.difficulty, pointsForThisQuestion, isCorrect);
849
- updateMap(bloomLevelPerformanceMap, q.bloomLevel, pointsForThisQuestion, isCorrect);
850
- });
851
- const formatPerformanceArray = (map, keyName) => {
852
- return Array.from(map.entries()).map(([key, data]) => ({
853
- [keyName]: key,
854
- totalQuestions: data.totalQuestions,
855
- correctQuestions: data.correctQuestions,
856
- pointsEarned: data.pointsEarned,
857
- maxPoints: data.maxPoints,
858
- percentage: data.maxPoints > 0 ? parseFloat((data.pointsEarned / data.maxPoints * 100).toFixed(2)) : 0
859
- }));
860
- };
861
- return {
862
- performanceByLearningObjective: formatPerformanceArray(loPerformanceMap, "learningObjective"),
863
- performanceByCategory: formatPerformanceArray(categoryPerformanceMap, "category"),
864
- performanceByTopic: formatPerformanceArray(topicPerformanceMap, "topic"),
865
- performanceByDifficulty: formatPerformanceArray(difficultyPerformanceMap, "difficulty"),
866
- performanceByBloomLevel: formatPerformanceArray(bloomLevelPerformanceMap, "bloomLevel")
867
- };
868
- }
869
- getElapsedTime() {
870
- return Date.now() - this.overallStartTime;
871
- }
872
- destroy() {
873
- this.stopTimer();
874
- this._recordCurrentQuestionTime();
875
- if (this.scormService && this.scormService.hasAPI()) {
876
- if (["initialized", "committed", "sending_data"].includes(this.quizResultState.scormStatus || "")) {
877
- const termResult = this.scormService.terminate();
878
- if (termResult.success) {
879
- this.quizResultState.scormStatus = "terminated";
880
- } else {
881
- this.quizResultState.scormStatus = "error";
882
- this.quizResultState.scormError = termResult.error || "SCORM termination failed on destroy.";
883
- }
884
- }
885
- }
886
- this.scormService = null;
887
- }
888
1585
  };
889
1586
  function cn(...inputs) {
890
1587
  return twMerge(clsx(inputs));
891
1588
  }
892
1589
 
893
1590
  // src/react-ui/components/elements/radio-group.tsx
894
- var RadioGroup = React25.forwardRef((_a, ref) => {
1591
+ var RadioGroup = React28.forwardRef((_a, ref) => {
895
1592
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
896
- return /* @__PURE__ */ React25.createElement(
1593
+ return /* @__PURE__ */ React28.createElement(
897
1594
  RadioGroupPrimitive.Root,
898
1595
  __spreadProps(__spreadValues({
899
1596
  className: cn("grid gap-2", className)
@@ -903,9 +1600,9 @@ var RadioGroup = React25.forwardRef((_a, ref) => {
903
1600
  );
904
1601
  });
905
1602
  RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
906
- var RadioGroupItem = React25.forwardRef((_a, ref) => {
1603
+ var RadioGroupItem = React28.forwardRef((_a, ref) => {
907
1604
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
908
- return /* @__PURE__ */ React25.createElement(
1605
+ return /* @__PURE__ */ React28.createElement(
909
1606
  RadioGroupPrimitive.Item,
910
1607
  __spreadValues({
911
1608
  ref,
@@ -914,16 +1611,16 @@ var RadioGroupItem = React25.forwardRef((_a, ref) => {
914
1611
  className
915
1612
  )
916
1613
  }, props),
917
- /* @__PURE__ */ React25.createElement(RadioGroupPrimitive.Indicator, { className: "flex items-center justify-center" }, /* @__PURE__ */ React25.createElement(Circle, { className: "h-2.5 w-2.5 fill-current text-current" }))
1614
+ /* @__PURE__ */ React28.createElement(RadioGroupPrimitive.Indicator, { className: "flex items-center justify-center" }, /* @__PURE__ */ React28.createElement(Circle, { className: "h-2.5 w-2.5 fill-current text-current" }))
918
1615
  );
919
1616
  });
920
1617
  RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
921
1618
  var labelVariants = cva(
922
1619
  "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
923
1620
  );
924
- var Label = React25.forwardRef((_a, ref) => {
1621
+ var Label = React28.forwardRef((_a, ref) => {
925
1622
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
926
- return /* @__PURE__ */ React25.createElement(
1623
+ return /* @__PURE__ */ React28.createElement(
927
1624
  LabelPrimitive.Root,
928
1625
  __spreadValues({
929
1626
  ref,
@@ -932,9 +1629,9 @@ var Label = React25.forwardRef((_a, ref) => {
932
1629
  );
933
1630
  });
934
1631
  Label.displayName = LabelPrimitive.Root.displayName;
935
- var Card = React25.forwardRef((_a, ref) => {
1632
+ var Card = React28.forwardRef((_a, ref) => {
936
1633
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
937
- return /* @__PURE__ */ React25.createElement(
1634
+ return /* @__PURE__ */ React28.createElement(
938
1635
  "div",
939
1636
  __spreadValues({
940
1637
  ref,
@@ -946,9 +1643,9 @@ var Card = React25.forwardRef((_a, ref) => {
946
1643
  );
947
1644
  });
948
1645
  Card.displayName = "Card";
949
- var CardHeader = React25.forwardRef((_a, ref) => {
1646
+ var CardHeader = React28.forwardRef((_a, ref) => {
950
1647
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
951
- return /* @__PURE__ */ React25.createElement(
1648
+ return /* @__PURE__ */ React28.createElement(
952
1649
  "div",
953
1650
  __spreadValues({
954
1651
  ref,
@@ -957,9 +1654,9 @@ var CardHeader = React25.forwardRef((_a, ref) => {
957
1654
  );
958
1655
  });
959
1656
  CardHeader.displayName = "CardHeader";
960
- var CardTitle = React25.forwardRef((_a, ref) => {
1657
+ var CardTitle = React28.forwardRef((_a, ref) => {
961
1658
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
962
- return /* @__PURE__ */ React25.createElement(
1659
+ return /* @__PURE__ */ React28.createElement(
963
1660
  "div",
964
1661
  __spreadValues({
965
1662
  ref,
@@ -971,9 +1668,9 @@ var CardTitle = React25.forwardRef((_a, ref) => {
971
1668
  );
972
1669
  });
973
1670
  CardTitle.displayName = "CardTitle";
974
- var CardDescription = React25.forwardRef((_a, ref) => {
1671
+ var CardDescription = React28.forwardRef((_a, ref) => {
975
1672
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
976
- return /* @__PURE__ */ React25.createElement(
1673
+ return /* @__PURE__ */ React28.createElement(
977
1674
  "div",
978
1675
  __spreadValues({
979
1676
  ref,
@@ -982,14 +1679,14 @@ var CardDescription = React25.forwardRef((_a, ref) => {
982
1679
  );
983
1680
  });
984
1681
  CardDescription.displayName = "CardDescription";
985
- var CardContent = React25.forwardRef((_a, ref) => {
1682
+ var CardContent = React28.forwardRef((_a, ref) => {
986
1683
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
987
- return /* @__PURE__ */ React25.createElement("div", __spreadValues({ ref, className: cn("p-6 pt-0", className) }, props));
1684
+ return /* @__PURE__ */ React28.createElement("div", __spreadValues({ ref, className: cn("p-6 pt-0", className) }, props));
988
1685
  });
989
1686
  CardContent.displayName = "CardContent";
990
- var CardFooter = React25.forwardRef((_a, ref) => {
1687
+ var CardFooter = React28.forwardRef((_a, ref) => {
991
1688
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
992
- return /* @__PURE__ */ React25.createElement(
1689
+ return /* @__PURE__ */ React28.createElement(
993
1690
  "div",
994
1691
  __spreadValues({
995
1692
  ref,
@@ -1040,7 +1737,7 @@ var MarkdownRenderer = ({
1040
1737
  const processedContent = processContentForVideos(content);
1041
1738
  return (
1042
1739
  // Using Tailwind Typography for beautiful default styling of markdown content
1043
- /* @__PURE__ */ React25__default.createElement("div", { className: `prose dark:prose-invert max-w-none ${className}` }, /* @__PURE__ */ React25__default.createElement(
1740
+ /* @__PURE__ */ React28__default.createElement("div", { className: `prose dark:prose-invert max-w-none ${className}` }, /* @__PURE__ */ React28__default.createElement(
1044
1741
  ReactMarkdown,
1045
1742
  {
1046
1743
  remarkPlugins: [remarkGfm, remarkMath],
@@ -1053,7 +1750,7 @@ var MarkdownRenderer = ({
1053
1750
  const { platform, id } = getVideoId(src);
1054
1751
  if (platform && id) {
1055
1752
  const videoSrc = platform === "youtube" ? `https://www.youtube.com/embed/${id}` : `https://player.vimeo.com/video/${id}`;
1056
- return /* @__PURE__ */ React25__default.createElement("div", { className: "aspect-w-16 aspect-h-9 my-4" }, /* @__PURE__ */ React25__default.createElement(
1753
+ return /* @__PURE__ */ React28__default.createElement("div", { className: "aspect-w-16 aspect-h-9 my-4" }, /* @__PURE__ */ React28__default.createElement(
1057
1754
  "iframe",
1058
1755
  {
1059
1756
  src: videoSrc,
@@ -1066,7 +1763,7 @@ var MarkdownRenderer = ({
1066
1763
  }
1067
1764
  return (
1068
1765
  // eslint-disable-next-line @next/next/no-img-element
1069
- /* @__PURE__ */ React25__default.createElement(
1766
+ /* @__PURE__ */ React28__default.createElement(
1070
1767
  "img",
1071
1768
  __spreadProps(__spreadValues({}, props), {
1072
1769
  style: {
@@ -1083,12 +1780,12 @@ var MarkdownRenderer = ({
1083
1780
  // Override the default table to add responsive wrapper
1084
1781
  table: (_c) => {
1085
1782
  var _d = _c, { node } = _d, props = __objRest(_d, ["node"]);
1086
- return /* @__PURE__ */ React25__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React25__default.createElement("table", __spreadProps(__spreadValues({}, props), { className: "my-4 w-full text-sm" })));
1783
+ return /* @__PURE__ */ React28__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React28__default.createElement("table", __spreadProps(__spreadValues({}, props), { className: "my-4 w-full text-sm" })));
1087
1784
  },
1088
1785
  // Override default blockquote for better styling
1089
1786
  blockquote: (_e) => {
1090
1787
  var _f = _e, { node } = _f, props = __objRest(_f, ["node"]);
1091
- return /* @__PURE__ */ React25__default.createElement(
1788
+ return /* @__PURE__ */ React28__default.createElement(
1092
1789
  "blockquote",
1093
1790
  __spreadProps(__spreadValues({}, props), {
1094
1791
  className: "border-l-4 border-primary bg-muted/50 p-4 my-4 italic"
@@ -1113,7 +1810,7 @@ var MultipleChoiceQuestionUI = ({
1113
1810
  const handleSelection = (value) => {
1114
1811
  onAnswerChange(value);
1115
1812
  };
1116
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__default.createElement(
1813
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__default.createElement(
1117
1814
  RadioGroup,
1118
1815
  {
1119
1816
  value: userAnswer || void 0,
@@ -1136,17 +1833,17 @@ var MultipleChoiceQuestionUI = ({
1136
1833
  } else {
1137
1834
  itemClassName += isSelected ? " border-primary bg-primary/10" : " border-muted";
1138
1835
  }
1139
- return /* @__PURE__ */ React25__default.createElement(
1836
+ return /* @__PURE__ */ React28__default.createElement(
1140
1837
  Label,
1141
1838
  {
1142
1839
  key: option.id,
1143
1840
  htmlFor: option.id,
1144
1841
  className: itemClassName
1145
1842
  },
1146
- /* @__PURE__ */ React25__default.createElement("div", { className: "flex items-center" }, /* @__PURE__ */ React25__default.createElement(RadioGroupItem, { value: option.id, id: option.id, className: "mr-3" }), /* @__PURE__ */ React25__default.createElement("div", { className: "text-base flex-1" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: option.text })))
1843
+ /* @__PURE__ */ React28__default.createElement("div", { className: "flex items-center" }, /* @__PURE__ */ React28__default.createElement(RadioGroupItem, { value: option.id, id: option.id, className: "mr-3" }), /* @__PURE__ */ React28__default.createElement("div", { className: "text-base flex-1" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: option.text })))
1147
1844
  );
1148
1845
  })
1149
- ), showCorrectAnswer && explanation && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" }))));
1846
+ ), showCorrectAnswer && explanation && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" }))));
1150
1847
  };
1151
1848
  var TrueFalseQuestionUI = ({
1152
1849
  question,
@@ -1162,7 +1859,7 @@ var TrueFalseQuestionUI = ({
1162
1859
  const handleSelection = (value) => {
1163
1860
  onAnswerChange(value);
1164
1861
  };
1165
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__default.createElement(
1862
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__default.createElement(
1166
1863
  RadioGroup,
1167
1864
  {
1168
1865
  value: userAnswer || void 0,
@@ -1185,21 +1882,21 @@ var TrueFalseQuestionUI = ({
1185
1882
  } else {
1186
1883
  itemClassName += isSelected ? " border-primary bg-primary/10" : " border-muted";
1187
1884
  }
1188
- return /* @__PURE__ */ React25__default.createElement(
1885
+ return /* @__PURE__ */ React28__default.createElement(
1189
1886
  Label,
1190
1887
  {
1191
1888
  key: option.id,
1192
1889
  htmlFor: option.id,
1193
1890
  className: itemClassName
1194
1891
  },
1195
- /* @__PURE__ */ React25__default.createElement("div", { className: "flex items-center" }, /* @__PURE__ */ React25__default.createElement(RadioGroupItem, { value: option.value, id: option.id, className: "mr-3" }), /* @__PURE__ */ React25__default.createElement("span", { className: "text-base" }, option.label))
1892
+ /* @__PURE__ */ React28__default.createElement("div", { className: "flex items-center" }, /* @__PURE__ */ React28__default.createElement(RadioGroupItem, { value: option.value, id: option.id, className: "mr-3" }), /* @__PURE__ */ React28__default.createElement("span", { className: "text-base" }, option.label))
1196
1893
  );
1197
1894
  })
1198
- ), showCorrectAnswer && explanation && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" }))));
1895
+ ), showCorrectAnswer && explanation && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" }))));
1199
1896
  };
1200
- var Checkbox = React25.forwardRef((_a, ref) => {
1897
+ var Checkbox = React28.forwardRef((_a, ref) => {
1201
1898
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1202
- return /* @__PURE__ */ React25.createElement(
1899
+ return /* @__PURE__ */ React28.createElement(
1203
1900
  CheckboxPrimitive.Root,
1204
1901
  __spreadValues({
1205
1902
  ref,
@@ -1208,12 +1905,12 @@ var Checkbox = React25.forwardRef((_a, ref) => {
1208
1905
  className
1209
1906
  )
1210
1907
  }, props),
1211
- /* @__PURE__ */ React25.createElement(
1908
+ /* @__PURE__ */ React28.createElement(
1212
1909
  CheckboxPrimitive.Indicator,
1213
1910
  {
1214
1911
  className: cn("flex items-center justify-center text-current")
1215
1912
  },
1216
- /* @__PURE__ */ React25.createElement(Check, { className: "h-4 w-4" })
1913
+ /* @__PURE__ */ React28.createElement(Check, { className: "h-4 w-4" })
1217
1914
  )
1218
1915
  );
1219
1916
  });
@@ -1241,7 +1938,7 @@ var MultipleResponseQuestionUI = ({
1241
1938
  }
1242
1939
  onAnswerChange(newAnswers.length > 0 ? newAnswers : null);
1243
1940
  };
1244
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__default.createElement("div", { className: "space-y-3", role: "group", "aria-labelledby": `question-prompt-${questionId}` }, options.map((option) => {
1941
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__default.createElement("div", { className: "space-y-3", role: "group", "aria-labelledby": `question-prompt-${questionId}` }, options.map((option) => {
1245
1942
  const isSelected = Array.isArray(userAnswer) && userAnswer.includes(option.id);
1246
1943
  const isCorrectOption = correctAnswerIds.includes(option.id);
1247
1944
  let itemClassName = "p-4 rounded-lg border-2 transition-all cursor-pointer hover:border-primary flex items-center";
@@ -1254,14 +1951,14 @@ var MultipleResponseQuestionUI = ({
1254
1951
  } else {
1255
1952
  itemClassName += isSelected ? " border-primary bg-primary/10" : " border-muted";
1256
1953
  }
1257
- return /* @__PURE__ */ React25__default.createElement(
1954
+ return /* @__PURE__ */ React28__default.createElement(
1258
1955
  Label,
1259
1956
  {
1260
1957
  key: option.id,
1261
1958
  htmlFor: option.id,
1262
1959
  className: itemClassName
1263
1960
  },
1264
- /* @__PURE__ */ React25__default.createElement(
1961
+ /* @__PURE__ */ React28__default.createElement(
1265
1962
  Checkbox,
1266
1963
  {
1267
1964
  id: option.id,
@@ -1271,14 +1968,14 @@ var MultipleResponseQuestionUI = ({
1271
1968
  "aria-label": option.text.replace(/<[^>]*>?/gm, "")
1272
1969
  }
1273
1970
  ),
1274
- /* @__PURE__ */ React25__default.createElement("div", { className: "text-base flex-1" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: option.text }))
1971
+ /* @__PURE__ */ React28__default.createElement("div", { className: "text-base flex-1" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: option.text }))
1275
1972
  );
1276
- })), showCorrectAnswer && explanation && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" }))));
1973
+ })), showCorrectAnswer && explanation && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" }))));
1277
1974
  };
1278
- var Input = React25.forwardRef(
1975
+ var Input = React28.forwardRef(
1279
1976
  (_a, ref) => {
1280
1977
  var _b = _a, { className, type } = _b, props = __objRest(_b, ["className", "type"]);
1281
- return /* @__PURE__ */ React25.createElement(
1978
+ return /* @__PURE__ */ React28.createElement(
1282
1979
  "input",
1283
1980
  __spreadValues({
1284
1981
  type,
@@ -1312,7 +2009,7 @@ var ShortAnswerQuestionUI = ({
1312
2009
  (accAns) => isCaseSensitive ? accAns.trim() === userAnswerTrimmed : accAns.trim().toLowerCase() === userAnswerTrimmed.toLowerCase()
1313
2010
  );
1314
2011
  }
1315
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__default.createElement(Label, { htmlFor: `short-answer-input-${questionId}`, className: "sr-only" }, "Your Answer"), /* @__PURE__ */ React25__default.createElement(
2012
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__default.createElement(Label, { htmlFor: `short-answer-input-${questionId}`, className: "sr-only" }, "Your Answer"), /* @__PURE__ */ React28__default.createElement(
1316
2013
  Input,
1317
2014
  {
1318
2015
  id: `short-answer-input-${questionId}`,
@@ -1325,7 +2022,7 @@ var ShortAnswerQuestionUI = ({
1325
2022
  ${showCorrectAnswer && userAnswer ? isActuallyCorrect ? "border-green-500 focus-visible:ring-green-500" : "border-destructive focus-visible:ring-destructive" : "border-input"}
1326
2023
  `
1327
2024
  }
1328
- ), showCorrectAnswer && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 space-y-2" }, userAnswer && !isActuallyCorrect && /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm text-destructive" }, "Your answer was marked incorrect."), /* @__PURE__ */ React25__default.createElement("div", { className: "p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Accepted Answers:"), /* @__PURE__ */ React25__default.createElement("ul", { className: "list-disc list-inside text-sm text-accent-foreground/80" }, acceptedAnswers.map((ans, idx) => /* @__PURE__ */ React25__default.createElement("li", { key: idx }, ans))), isCaseSensitive && /* @__PURE__ */ React25__default.createElement("p", { className: "text-xs text-muted-foreground mt-1" }, "(Case-sensitive)")), explanation && /* @__PURE__ */ React25__default.createElement("div", { id: `explanation-${questionId}`, className: "mt-2 p-3 bg-muted/30 border border-muted rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold" }, "Explanation:"), /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-muted-foreground" })))));
2025
+ ), showCorrectAnswer && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 space-y-2" }, userAnswer && !isActuallyCorrect && /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm text-destructive" }, "Your answer was marked incorrect."), /* @__PURE__ */ React28__default.createElement("div", { className: "p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Accepted Answers:"), /* @__PURE__ */ React28__default.createElement("ul", { className: "list-disc list-inside text-sm text-accent-foreground/80" }, acceptedAnswers.map((ans, idx) => /* @__PURE__ */ React28__default.createElement("li", { key: idx }, ans))), isCaseSensitive && /* @__PURE__ */ React28__default.createElement("p", { className: "text-xs text-muted-foreground mt-1" }, "(Case-sensitive)")), explanation && /* @__PURE__ */ React28__default.createElement("div", { id: `explanation-${questionId}`, className: "mt-2 p-3 bg-muted/30 border border-muted rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold" }, "Explanation:"), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-muted-foreground" })))));
1329
2026
  };
1330
2027
  var NumericQuestionUI = ({
1331
2028
  question,
@@ -1348,7 +2045,7 @@ var NumericQuestionUI = ({
1348
2045
  isActuallyCorrect = tolerance !== void 0 && tolerance !== null ? Math.abs(userAnswerNum - correctAnswerValue) <= tolerance : userAnswerNum === correctAnswerValue;
1349
2046
  }
1350
2047
  }
1351
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__default.createElement(Label, { htmlFor: `numeric-input-${questionId}`, className: "sr-only" }, "Your Answer"), /* @__PURE__ */ React25__default.createElement(
2048
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__default.createElement(Label, { htmlFor: `numeric-input-${questionId}`, className: "sr-only" }, "Your Answer"), /* @__PURE__ */ React28__default.createElement(
1352
2049
  Input,
1353
2050
  {
1354
2051
  id: `numeric-input-${questionId}`,
@@ -1363,7 +2060,7 @@ var NumericQuestionUI = ({
1363
2060
  ${showCorrectAnswer && userAnswer !== null && userAnswer !== "" ? isActuallyCorrect ? "border-green-500 focus-visible:ring-green-500" : "border-destructive focus-visible:ring-destructive" : "border-input"}
1364
2061
  `
1365
2062
  }
1366
- ), showCorrectAnswer && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 space-y-2" }, userAnswer !== null && userAnswer !== "" && !isActuallyCorrect && /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm text-destructive" }, "Your answer was marked incorrect."), /* @__PURE__ */ React25__default.createElement("div", { className: "p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Correct Answer:"), /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm text-accent-foreground/80" }, correctAnswerValue, tolerance !== void 0 && tolerance !== null && tolerance > 0 && ` (Tolerance: \xB1${tolerance}, Accepted range: ${correctAnswerValue - tolerance} to ${correctAnswerValue + tolerance})`)), explanation && /* @__PURE__ */ React25__default.createElement("div", { id: `explanation-${questionId}`, className: "mt-2 p-3 bg-muted/30 border border-muted rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold" }, "Explanation:"), /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-muted-foreground" })))));
2063
+ ), showCorrectAnswer && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 space-y-2" }, userAnswer !== null && userAnswer !== "" && !isActuallyCorrect && /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm text-destructive" }, "Your answer was marked incorrect."), /* @__PURE__ */ React28__default.createElement("div", { className: "p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Correct Answer:"), /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm text-accent-foreground/80" }, correctAnswerValue, tolerance !== void 0 && tolerance !== null && tolerance > 0 && ` (Tolerance: \xB1${tolerance}, Accepted range: ${correctAnswerValue - tolerance} to ${correctAnswerValue + tolerance})`)), explanation && /* @__PURE__ */ React28__default.createElement("div", { id: `explanation-${questionId}`, className: "mt-2 p-3 bg-muted/30 border border-muted rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold" }, "Explanation:"), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-muted-foreground" })))));
1367
2064
  };
1368
2065
  var FillInTheBlanksQuestionUI = ({
1369
2066
  question,
@@ -1403,10 +2100,10 @@ var FillInTheBlanksQuestionUI = ({
1403
2100
  (accVal) => caseSensitive ? accVal.trim() === userAnswerForBlank : accVal.trim().toLowerCase() === userAnswerForBlank.toLowerCase()
1404
2101
  );
1405
2102
  };
1406
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__default.createElement("div", { className: "text-base leading-relaxed flex flex-wrap items-center gap-x-1", "aria-labelledby": `question-prompt-${questionId}` }, segments.map((segment, index) => {
2103
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__default.createElement("div", { className: "text-base leading-relaxed flex flex-wrap items-center gap-x-1", "aria-labelledby": `question-prompt-${questionId}` }, segments.map((segment, index) => {
1407
2104
  var _a;
1408
2105
  if (segment.type === "text") {
1409
- return /* @__PURE__ */ React25__default.createElement(
2106
+ return /* @__PURE__ */ React28__default.createElement(
1410
2107
  MarkdownRenderer,
1411
2108
  {
1412
2109
  key: `text-${index}`,
@@ -1424,7 +2121,7 @@ var FillInTheBlanksQuestionUI = ({
1424
2121
  } else {
1425
2122
  inputClassName += " border-input";
1426
2123
  }
1427
- return /* @__PURE__ */ React25__default.createElement(
2124
+ return /* @__PURE__ */ React28__default.createElement(
1428
2125
  Input,
1429
2126
  {
1430
2127
  key: blankId,
@@ -1440,11 +2137,11 @@ var FillInTheBlanksQuestionUI = ({
1440
2137
  );
1441
2138
  }
1442
2139
  return null;
1443
- })), showCorrectAnswer && explanation && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-6 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Gi\u1EA3i th\xEDch chung:"), /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 space-y-3" }, correctAnswersMap.map((ansDef) => {
2140
+ })), showCorrectAnswer && explanation && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-6 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Gi\u1EA3i th\xEDch chung:"), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 space-y-3" }, correctAnswersMap.map((ansDef) => {
1444
2141
  var _a;
1445
2142
  const isBlankCorrect = getCorrectnessForBlank(ansDef.blankId);
1446
2143
  const userAnswerDisplay = userInputs[ansDef.blankId] || "Ch\u01B0a tr\u1EA3 l\u1EDDi";
1447
- return /* @__PURE__ */ React25__default.createElement("div", { key: `feedback-${ansDef.blankId}`, className: `p-2 border rounded-md ${isBlankCorrect ? "border-green-500/50 bg-green-500/10" : "border-destructive/50 bg-destructive/10"}` }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React25__default.createElement("span", { className: "font-semibold" }, "\xD4 tr\u1ED1ng '", ((_a = segments.find((s) => s.id === ansDef.blankId && s.type === "blank")) == null ? void 0 : _a.id) || ansDef.blankId, "':"), ' B\u1EA1n \u0111\xE3 \u0111i\u1EC1n: "', userAnswerDisplay, '".'), /* @__PURE__ */ React25__default.createElement("p", { className: "text-xs" }, "\u0110\xE1p \xE1n ch\u1EA5p nh\u1EADn: ", ansDef.acceptedValues.join(", ")));
2144
+ return /* @__PURE__ */ React28__default.createElement("div", { key: `feedback-${ansDef.blankId}`, className: `p-2 border rounded-md ${isBlankCorrect ? "border-green-500/50 bg-green-500/10" : "border-destructive/50 bg-destructive/10"}` }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React28__default.createElement("span", { className: "font-semibold" }, "\xD4 tr\u1ED1ng '", ((_a = segments.find((s) => s.id === ansDef.blankId && s.type === "blank")) == null ? void 0 : _a.id) || ansDef.blankId, "':"), ' B\u1EA1n \u0111\xE3 \u0111i\u1EC1n: "', userAnswerDisplay, '".'), /* @__PURE__ */ React28__default.createElement("p", { className: "text-xs" }, "\u0110\xE1p \xE1n ch\u1EA5p nh\u1EADn: ", ansDef.acceptedValues.join(", ")));
1448
2145
  }))));
1449
2146
  };
1450
2147
  var buttonVariants = cva(
@@ -1472,11 +2169,11 @@ var buttonVariants = cva(
1472
2169
  }
1473
2170
  }
1474
2171
  );
1475
- var Button = React25.forwardRef(
2172
+ var Button = React28.forwardRef(
1476
2173
  (_a, ref) => {
1477
2174
  var _b = _a, { className, variant, size, asChild = false } = _b, props = __objRest(_b, ["className", "variant", "size", "asChild"]);
1478
2175
  const Comp = asChild ? Slot : "button";
1479
- return /* @__PURE__ */ React25.createElement(
2176
+ return /* @__PURE__ */ React28.createElement(
1480
2177
  Comp,
1481
2178
  __spreadValues({
1482
2179
  className: cn(buttonVariants({ variant, size, className })),
@@ -1536,37 +2233,37 @@ var SequenceQuestionUI = ({
1536
2233
  if (!showCorrectAnswer || !Array.isArray(userAnswer) || userAnswer.length <= index) return null;
1537
2234
  const userItemId = userAnswer[index];
1538
2235
  const correctItemId = correctOrder[index];
1539
- return userItemId === correctItemId ? /* @__PURE__ */ React25__default.createElement(CheckCircle, { className: "h-4 w-4 text-green-500 ml-2 flex-shrink-0" }) : /* @__PURE__ */ React25__default.createElement(XCircle, { className: "h-4 w-4 text-destructive ml-2 flex-shrink-0" });
2236
+ return userItemId === correctItemId ? /* @__PURE__ */ React28__default.createElement(CheckCircle, { className: "h-4 w-4 text-green-500 ml-2 flex-shrink-0" }) : /* @__PURE__ */ React28__default.createElement(XCircle, { className: "h-4 w-4 text-destructive ml-2 flex-shrink-0" });
1540
2237
  };
1541
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "p-0 space-y-6" }, /* @__PURE__ */ React25__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React25__default.createElement(Label, { className: "font-semibold" }, "S\u1EAFp x\u1EBFp c\xE1c m\u1EE5c sau theo \u0111\xFAng th\u1EE9 t\u1EF1:"), /* @__PURE__ */ React25__default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2" }, availableItems.map((item) => /* @__PURE__ */ React25__default.createElement(
2238
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0 space-y-6" }, /* @__PURE__ */ React28__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React28__default.createElement(Label, { className: "font-semibold" }, "S\u1EAFp x\u1EBFp c\xE1c m\u1EE5c sau theo \u0111\xFAng th\u1EE9 t\u1EF1:"), /* @__PURE__ */ React28__default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2" }, availableItems.map((item) => /* @__PURE__ */ React28__default.createElement(
1542
2239
  Button,
1543
2240
  {
1544
2241
  key: item.id,
1545
2242
  variant: "outline",
1546
2243
  onClick: () => handleSelectItem(item),
1547
- className: "justify-start text-left h-auto py-2 px-3",
2244
+ className: "justify-start text-left h-auto py-2 px-3 whitespace-normal",
1548
2245
  disabled: showCorrectAnswer
1549
2246
  },
1550
- /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: item.content })
1551
- ))), availableItems.length === 0 && selectedSequence.length > 0 && /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm text-muted-foreground" }, 'T\u1EA5t c\u1EA3 c\xE1c m\u1EE5c \u0111\xE3 \u0111\u01B0\u1EE3c ch\u1ECDn. Nh\u1EA5p v\xE0o m\u1EE5c trong "Th\u1EE9 t\u1EF1 b\u1EA1n \u0111\xE3 ch\u1ECDn" \u0111\u1EC3 b\u1ECF ch\u1ECDn.')), /* @__PURE__ */ React25__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React25__default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React25__default.createElement(Label, { className: "font-semibold" }, "Th\u1EE9 t\u1EF1 b\u1EA1n \u0111\xE3 ch\u1ECDn:"), /* @__PURE__ */ React25__default.createElement(Button, { variant: "ghost", size: "sm", onClick: handleResetSequence, disabled: showCorrectAnswer || selectedSequence.length === 0 }, /* @__PURE__ */ React25__default.createElement(RotateCcw, { className: "mr-2 h-3.5 w-3.5" }), " \u0110\u1EB7t l\u1EA1i")), selectedSequence.length === 0 ? /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm text-muted-foreground p-3 border border-dashed rounded-md" }, "Ch\u01B0a ch\u1ECDn m\u1EE5c n\xE0o. Nh\u1EA5p v\xE0o c\xE1c m\u1EE5c \u1EDF tr\xEAn \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u.") : /* @__PURE__ */ React25__default.createElement("ul", { className: "space-y-2" }, selectedSequence.map((item, index) => /* @__PURE__ */ React25__default.createElement(
2247
+ /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: item.content })
2248
+ ))), availableItems.length === 0 && selectedSequence.length > 0 && /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm text-muted-foreground" }, 'T\u1EA5t c\u1EA3 c\xE1c m\u1EE5c \u0111\xE3 \u0111\u01B0\u1EE3c ch\u1ECDn. Nh\u1EA5p v\xE0o m\u1EE5c trong "Th\u1EE9 t\u1EF1 b\u1EA1n \u0111\xE3 ch\u1ECDn" \u0111\u1EC3 b\u1ECF ch\u1ECDn.')), /* @__PURE__ */ React28__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React28__default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React28__default.createElement(Label, { className: "font-semibold" }, "Th\u1EE9 t\u1EF1 b\u1EA1n \u0111\xE3 ch\u1ECDn:"), /* @__PURE__ */ React28__default.createElement(Button, { variant: "ghost", size: "sm", onClick: handleResetSequence, disabled: showCorrectAnswer || selectedSequence.length === 0 }, /* @__PURE__ */ React28__default.createElement(RotateCcw, { className: "mr-2 h-3.5 w-3.5" }), " \u0110\u1EB7t l\u1EA1i")), selectedSequence.length === 0 ? /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm text-muted-foreground p-3 border border-dashed rounded-md" }, "Ch\u01B0a ch\u1ECDn m\u1EE5c n\xE0o. Nh\u1EA5p v\xE0o c\xE1c m\u1EE5c \u1EDF tr\xEAn \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u.") : /* @__PURE__ */ React28__default.createElement("ul", { className: "space-y-2" }, selectedSequence.map((item, index) => /* @__PURE__ */ React28__default.createElement(
1552
2249
  "li",
1553
2250
  {
1554
2251
  key: item.id,
1555
2252
  onClick: () => handleRemoveFromSequence(item, index),
1556
- className: `flex items-center justify-between p-3 border rounded-md ${showCorrectAnswer ? (userAnswer == null ? void 0 : userAnswer[index]) === correctOrder[index] ? "border-green-500 bg-green-500/10" : "border-destructive bg-destructive/10" : "bg-muted/30 cursor-pointer hover:border-destructive/50"} transition-colors`
2253
+ className: `flex items-center justify-between p-3 border rounded-md whitespace-normal ${showCorrectAnswer ? (userAnswer == null ? void 0 : userAnswer[index]) === correctOrder[index] ? "border-green-500 bg-green-500/10" : "border-destructive bg-destructive/10" : "bg-muted/30 cursor-pointer hover:border-destructive/50"} transition-colors`
1557
2254
  },
1558
- /* @__PURE__ */ React25__default.createElement("div", { className: "flex-grow flex items-center" }, /* @__PURE__ */ React25__default.createElement("span", { className: "font-semibold mr-2" }, index + 1, "."), /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: item.content })),
1559
- showCorrectAnswer ? getFeedbackIcon(index) : /* @__PURE__ */ React25__default.createElement(XCircle, { className: "h-4 w-4 text-muted-foreground hover:text-destructive flex-shrink-0" })
1560
- )))), showCorrectAnswer && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React25__default.createElement("div", { className: "p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Th\u1EE9 t\u1EF1 \u0111\xFAng:"), /* @__PURE__ */ React25__default.createElement("ol", { className: "list-decimal list-inside text-sm text-accent-foreground/80 space-y-1 mt-1" }, correctOrder.map((itemId) => {
2255
+ /* @__PURE__ */ React28__default.createElement("div", { className: "flex-grow flex items-center" }, /* @__PURE__ */ React28__default.createElement("span", { className: "font-semibold mr-2" }, index + 1, "."), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: item.content })),
2256
+ showCorrectAnswer ? getFeedbackIcon(index) : /* @__PURE__ */ React28__default.createElement(XCircle, { className: "h-4 w-4 text-muted-foreground hover:text-destructive flex-shrink-0" })
2257
+ )))), showCorrectAnswer && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React28__default.createElement("div", { className: "p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Th\u1EE9 t\u1EF1 \u0111\xFAng:"), /* @__PURE__ */ React28__default.createElement("ol", { className: "list-decimal list-inside text-sm text-accent-foreground/80 space-y-1 mt-1" }, correctOrder.map((itemId) => {
1561
2258
  const item = items.find((i) => i.id === itemId);
1562
- return /* @__PURE__ */ React25__default.createElement("li", { key: itemId }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: item ? item.content : "Kh\xF4ng t\xECm th\u1EA5y m\u1EE5c" }));
1563
- }))), explanation && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-2 p-3 bg-muted/30 border border-muted rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold" }, "Gi\u1EA3i th\xEDch:"), /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-muted-foreground" })))));
2259
+ return /* @__PURE__ */ React28__default.createElement("li", { key: itemId }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: item ? item.content : "Kh\xF4ng t\xECm th\u1EA5y m\u1EE5c" }));
2260
+ }))), explanation && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-2 p-3 bg-muted/30 border border-muted rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold" }, "Gi\u1EA3i th\xEDch:"), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-muted-foreground" })))));
1564
2261
  };
1565
2262
  var Select = SelectPrimitive.Root;
1566
2263
  var SelectValue = SelectPrimitive.Value;
1567
- var SelectTrigger = React25.forwardRef((_a, ref) => {
2264
+ var SelectTrigger = React28.forwardRef((_a, ref) => {
1568
2265
  var _b = _a, { className, children } = _b, props = __objRest(_b, ["className", "children"]);
1569
- return /* @__PURE__ */ React25.createElement(
2266
+ return /* @__PURE__ */ React28.createElement(
1570
2267
  SelectPrimitive.Trigger,
1571
2268
  __spreadValues({
1572
2269
  ref,
@@ -1576,13 +2273,13 @@ var SelectTrigger = React25.forwardRef((_a, ref) => {
1576
2273
  )
1577
2274
  }, props),
1578
2275
  children,
1579
- /* @__PURE__ */ React25.createElement(SelectPrimitive.Icon, { asChild: true }, /* @__PURE__ */ React25.createElement(ChevronDown, { className: "h-4 w-4 opacity-50" }))
2276
+ /* @__PURE__ */ React28.createElement(SelectPrimitive.Icon, { asChild: true }, /* @__PURE__ */ React28.createElement(ChevronDown, { className: "h-4 w-4 opacity-50" }))
1580
2277
  );
1581
2278
  });
1582
2279
  SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
1583
- var SelectScrollUpButton = React25.forwardRef((_a, ref) => {
2280
+ var SelectScrollUpButton = React28.forwardRef((_a, ref) => {
1584
2281
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1585
- return /* @__PURE__ */ React25.createElement(
2282
+ return /* @__PURE__ */ React28.createElement(
1586
2283
  SelectPrimitive.ScrollUpButton,
1587
2284
  __spreadValues({
1588
2285
  ref,
@@ -1591,13 +2288,13 @@ var SelectScrollUpButton = React25.forwardRef((_a, ref) => {
1591
2288
  className
1592
2289
  )
1593
2290
  }, props),
1594
- /* @__PURE__ */ React25.createElement(ChevronUp, { className: "h-4 w-4" })
2291
+ /* @__PURE__ */ React28.createElement(ChevronUp, { className: "h-4 w-4" })
1595
2292
  );
1596
2293
  });
1597
2294
  SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
1598
- var SelectScrollDownButton = React25.forwardRef((_a, ref) => {
2295
+ var SelectScrollDownButton = React28.forwardRef((_a, ref) => {
1599
2296
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1600
- return /* @__PURE__ */ React25.createElement(
2297
+ return /* @__PURE__ */ React28.createElement(
1601
2298
  SelectPrimitive.ScrollDownButton,
1602
2299
  __spreadValues({
1603
2300
  ref,
@@ -1606,13 +2303,13 @@ var SelectScrollDownButton = React25.forwardRef((_a, ref) => {
1606
2303
  className
1607
2304
  )
1608
2305
  }, props),
1609
- /* @__PURE__ */ React25.createElement(ChevronDown, { className: "h-4 w-4" })
2306
+ /* @__PURE__ */ React28.createElement(ChevronDown, { className: "h-4 w-4" })
1610
2307
  );
1611
2308
  });
1612
2309
  SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
1613
- var SelectContent = React25.forwardRef((_a, ref) => {
2310
+ var SelectContent = React28.forwardRef((_a, ref) => {
1614
2311
  var _b = _a, { className, children, position = "popper" } = _b, props = __objRest(_b, ["className", "children", "position"]);
1615
- return /* @__PURE__ */ React25.createElement(SelectPrimitive.Portal, null, /* @__PURE__ */ React25.createElement(
2312
+ return /* @__PURE__ */ React28.createElement(SelectPrimitive.Portal, null, /* @__PURE__ */ React28.createElement(
1616
2313
  SelectPrimitive.Content,
1617
2314
  __spreadValues({
1618
2315
  ref,
@@ -1623,8 +2320,8 @@ var SelectContent = React25.forwardRef((_a, ref) => {
1623
2320
  ),
1624
2321
  position
1625
2322
  }, props),
1626
- /* @__PURE__ */ React25.createElement(SelectScrollUpButton, null),
1627
- /* @__PURE__ */ React25.createElement(
2323
+ /* @__PURE__ */ React28.createElement(SelectScrollUpButton, null),
2324
+ /* @__PURE__ */ React28.createElement(
1628
2325
  SelectPrimitive.Viewport,
1629
2326
  {
1630
2327
  className: cn(
@@ -1634,13 +2331,13 @@ var SelectContent = React25.forwardRef((_a, ref) => {
1634
2331
  },
1635
2332
  children
1636
2333
  ),
1637
- /* @__PURE__ */ React25.createElement(SelectScrollDownButton, null)
2334
+ /* @__PURE__ */ React28.createElement(SelectScrollDownButton, null)
1638
2335
  ));
1639
2336
  });
1640
2337
  SelectContent.displayName = SelectPrimitive.Content.displayName;
1641
- var SelectLabel = React25.forwardRef((_a, ref) => {
2338
+ var SelectLabel = React28.forwardRef((_a, ref) => {
1642
2339
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1643
- return /* @__PURE__ */ React25.createElement(
2340
+ return /* @__PURE__ */ React28.createElement(
1644
2341
  SelectPrimitive.Label,
1645
2342
  __spreadValues({
1646
2343
  ref,
@@ -1649,9 +2346,9 @@ var SelectLabel = React25.forwardRef((_a, ref) => {
1649
2346
  );
1650
2347
  });
1651
2348
  SelectLabel.displayName = SelectPrimitive.Label.displayName;
1652
- var SelectItem = React25.forwardRef((_a, ref) => {
2349
+ var SelectItem = React28.forwardRef((_a, ref) => {
1653
2350
  var _b = _a, { className, children } = _b, props = __objRest(_b, ["className", "children"]);
1654
- return /* @__PURE__ */ React25.createElement(
2351
+ return /* @__PURE__ */ React28.createElement(
1655
2352
  SelectPrimitive.Item,
1656
2353
  __spreadValues({
1657
2354
  ref,
@@ -1660,14 +2357,14 @@ var SelectItem = React25.forwardRef((_a, ref) => {
1660
2357
  className
1661
2358
  )
1662
2359
  }, props),
1663
- /* @__PURE__ */ React25.createElement("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" }, /* @__PURE__ */ React25.createElement(SelectPrimitive.ItemIndicator, null, /* @__PURE__ */ React25.createElement(Check, { className: "h-4 w-4" }))),
1664
- /* @__PURE__ */ React25.createElement(SelectPrimitive.ItemText, null, children)
2360
+ /* @__PURE__ */ React28.createElement("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" }, /* @__PURE__ */ React28.createElement(SelectPrimitive.ItemIndicator, null, /* @__PURE__ */ React28.createElement(Check, { className: "h-4 w-4" }))),
2361
+ /* @__PURE__ */ React28.createElement(SelectPrimitive.ItemText, null, children)
1665
2362
  );
1666
2363
  });
1667
2364
  SelectItem.displayName = SelectPrimitive.Item.displayName;
1668
- var SelectSeparator = React25.forwardRef((_a, ref) => {
2365
+ var SelectSeparator = React28.forwardRef((_a, ref) => {
1669
2366
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1670
- return /* @__PURE__ */ React25.createElement(
2367
+ return /* @__PURE__ */ React28.createElement(
1671
2368
  SelectPrimitive.Separator,
1672
2369
  __spreadValues({
1673
2370
  ref,
@@ -1720,7 +2417,7 @@ var MatchingQuestionUI = ({
1720
2417
  }
1721
2418
  return htmlString.replace(/<[^>]*>?/gm, "");
1722
2419
  };
1723
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "p-0 space-y-4" }, /* @__PURE__ */ React25__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-4" }, prompts.map((promptItem) => {
2420
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0 space-y-4" }, /* @__PURE__ */ React28__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-4" }, prompts.map((promptItem) => {
1724
2421
  var _a;
1725
2422
  const selectedOptionId = currentAnswers[promptItem.id] || "";
1726
2423
  const correctOptionId = getCorrectOptionIdForPrompt(promptItem.id);
@@ -1729,21 +2426,21 @@ var MatchingQuestionUI = ({
1729
2426
  if (showCorrectAnswer && selectedOptionId) {
1730
2427
  borderColor = isSelectionCorrect ? "border-green-500" : "border-destructive";
1731
2428
  }
1732
- return /* @__PURE__ */ React25__default.createElement("div", { key: promptItem.id, className: `p-3 border rounded-md ${borderColor} transition-colors bg-background` }, /* @__PURE__ */ React25__default.createElement(Label, { htmlFor: `select-prompt-${promptItem.id}`, className: "font-medium text-base block mb-2" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: promptItem.content })), /* @__PURE__ */ React25__default.createElement(
2429
+ return /* @__PURE__ */ React28__default.createElement("div", { key: promptItem.id, className: `p-3 border rounded-md ${borderColor} transition-colors bg-background` }, /* @__PURE__ */ React28__default.createElement(Label, { htmlFor: `select-prompt-${promptItem.id}`, className: "font-medium text-base block mb-2 whitespace-normal" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: promptItem.content })), /* @__PURE__ */ React28__default.createElement(
1733
2430
  Select,
1734
2431
  {
1735
2432
  value: selectedOptionId,
1736
2433
  onValueChange: (value) => handleSelectChange(promptItem.id, value),
1737
2434
  disabled: showCorrectAnswer
1738
2435
  },
1739
- /* @__PURE__ */ React25__default.createElement(SelectTrigger, { id: `select-prompt-${promptItem.id}` }, /* @__PURE__ */ React25__default.createElement(SelectValue, { placeholder: "Ch\u1ECDn \u0111\xE1p \xE1n..." })),
1740
- /* @__PURE__ */ React25__default.createElement(SelectContent, null, shuffledOptions.map((option) => /* @__PURE__ */ React25__default.createElement(SelectItem, { key: option.id, value: option.id }, getPlainText(option.content))))
1741
- ), showCorrectAnswer && selectedOptionId && (isSelectionCorrect ? /* @__PURE__ */ React25__default.createElement(CheckCircle, { className: "h-5 w-5 text-green-500 mt-2 inline-block" }) : /* @__PURE__ */ React25__default.createElement(XCircle, { className: "h-5 w-5 text-destructive mt-2 inline-block" })), showCorrectAnswer && !selectedOptionId && correctOptionId && /* @__PURE__ */ React25__default.createElement("p", { className: "text-xs text-muted-foreground mt-1" }, "Ch\u01B0a ch\u1ECDn. \u0110\xE1p \xE1n \u0111\xFAng: ", getPlainText((_a = shuffledOptions.find((o) => o.id === correctOptionId)) == null ? void 0 : _a.content)));
1742
- })), showCorrectAnswer && explanation && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-6 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Gi\u1EA3i th\xEDch:"), /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React25__default.createElement("p", { className: "font-semibold text-md" }, "\u0110\xE1p \xE1n \u0111\xFAng:"), /* @__PURE__ */ React25__default.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" }, correctAnswerMap.map((map) => {
2436
+ /* @__PURE__ */ React28__default.createElement(SelectTrigger, { id: `select-prompt-${promptItem.id}` }, /* @__PURE__ */ React28__default.createElement(SelectValue, { placeholder: "Ch\u1ECDn \u0111\xE1p \xE1n..." })),
2437
+ /* @__PURE__ */ React28__default.createElement(SelectContent, null, shuffledOptions.map((option) => /* @__PURE__ */ React28__default.createElement(SelectItem, { key: option.id, value: option.id, className: "whitespace-normal" }, getPlainText(option.content))))
2438
+ ), showCorrectAnswer && selectedOptionId && (isSelectionCorrect ? /* @__PURE__ */ React28__default.createElement(CheckCircle, { className: "h-5 w-5 text-green-500 mt-2 inline-block" }) : /* @__PURE__ */ React28__default.createElement(XCircle, { className: "h-5 w-5 text-destructive mt-2 inline-block" })), showCorrectAnswer && !selectedOptionId && correctOptionId && /* @__PURE__ */ React28__default.createElement("p", { className: "text-xs text-muted-foreground mt-1" }, "Ch\u01B0a ch\u1ECDn. \u0110\xE1p \xE1n \u0111\xFAng: ", getPlainText((_a = shuffledOptions.find((o) => o.id === correctOptionId)) == null ? void 0 : _a.content)));
2439
+ })), showCorrectAnswer && explanation && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-6 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Gi\u1EA3i th\xEDch:"), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React28__default.createElement("p", { className: "font-semibold text-md" }, "\u0110\xE1p \xE1n \u0111\xFAng:"), /* @__PURE__ */ React28__default.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" }, correctAnswerMap.map((map) => {
1743
2440
  var _a, _b;
1744
2441
  const promptText = (_a = prompts.find((p) => p.id === map.promptId)) == null ? void 0 : _a.content;
1745
2442
  const optionText = (_b = initialOptions.find((o) => o.id === map.optionId)) == null ? void 0 : _b.content;
1746
- return /* @__PURE__ */ React25__default.createElement("li", { key: map.promptId }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: `<strong>${getPlainText(promptText) || "N/A"}</strong> gh\xE9p v\u1EDBi <strong>${getPlainText(optionText) || "N/A"}</strong>` }));
2443
+ return /* @__PURE__ */ React28__default.createElement("li", { key: map.promptId }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: `<strong>${getPlainText(promptText) || "N/A"}</strong> gh\xE9p v\u1EDBi <strong>${getPlainText(optionText) || "N/A"}</strong>` }));
1747
2444
  })))));
1748
2445
  };
1749
2446
  var DragAndDropQuestionUI = ({
@@ -1773,7 +2470,7 @@ var DragAndDropQuestionUI = ({
1773
2470
  var _a;
1774
2471
  return (_a = answerMap.find((map) => map.draggableId === draggableItemId)) == null ? void 0 : _a.dropZoneId;
1775
2472
  };
1776
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "p-0 space-y-4" }, backgroundImageUrl && /* @__PURE__ */ React25__default.createElement("div", { className: "mb-4 overflow-hidden rounded-md border" }, /* @__PURE__ */ React25__default.createElement("img", { src: backgroundImageUrl, alt: question.imageAltText || "Drag and drop background", className: "w-full h-auto object-contain max-h-[300px]", "data-ai-hint": "abstract pattern" })), /* @__PURE__ */ React25__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React25__default.createElement(Label, { className: "font-semibold" }, "Gh\xE9p c\xE1c m\u1EE5c sau v\xE0o \u0111\xFAng v\u1ECB tr\xED:"), draggableItems.map((item) => {
2473
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0 space-y-4" }, backgroundImageUrl && /* @__PURE__ */ React28__default.createElement("div", { className: "mb-4 overflow-hidden rounded-md border" }, /* @__PURE__ */ React28__default.createElement("img", { src: backgroundImageUrl, alt: question.imageAltText || "Drag and drop background", className: "w-full h-auto object-contain max-h-[300px]", "data-ai-hint": "abstract pattern" })), /* @__PURE__ */ React28__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React28__default.createElement(Label, { className: "font-semibold" }, "Gh\xE9p c\xE1c m\u1EE5c sau v\xE0o \u0111\xFAng v\u1ECB tr\xED:"), draggableItems.map((item) => {
1777
2474
  const selectedDropZoneId = currentAnswers[item.id] || "";
1778
2475
  const correctDropZoneId = getCorrectDropZoneIdForDraggable(item.id);
1779
2476
  const isSelectionCorrect = showCorrectAnswer && selectedDropZoneId ? selectedDropZoneId === correctDropZoneId : null;
@@ -1783,21 +2480,21 @@ var DragAndDropQuestionUI = ({
1783
2480
  } else {
1784
2481
  itemStyle += " border-muted";
1785
2482
  }
1786
- return /* @__PURE__ */ React25__default.createElement("div", { key: item.id, className: itemStyle }, /* @__PURE__ */ React25__default.createElement(Label, { htmlFor: `select-draggable-${item.id}`, className: "font-medium text-base mb-2 sm:mb-0 sm:mr-4 flex-1" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: item.content })), /* @__PURE__ */ React25__default.createElement("div", { className: "flex items-center space-x-2 w-full sm:w-auto" }, /* @__PURE__ */ React25__default.createElement(
2483
+ return /* @__PURE__ */ React28__default.createElement("div", { key: item.id, className: itemStyle }, /* @__PURE__ */ React28__default.createElement(Label, { htmlFor: `select-draggable-${item.id}`, className: "font-medium text-base mb-2 sm:mb-0 sm:mr-4 flex-1" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: item.content })), /* @__PURE__ */ React28__default.createElement("div", { className: "flex items-center space-x-2 w-full sm:w-auto" }, /* @__PURE__ */ React28__default.createElement(
1787
2484
  Select,
1788
2485
  {
1789
2486
  value: selectedDropZoneId,
1790
2487
  onValueChange: (value) => handleSelectChange(item.id, value),
1791
2488
  disabled: showCorrectAnswer
1792
2489
  },
1793
- /* @__PURE__ */ React25__default.createElement(SelectTrigger, { id: `select-draggable-${item.id}`, className: "w-full sm:min-w-[200px]" }, /* @__PURE__ */ React25__default.createElement(SelectValue, { placeholder: "Ch\u1ECDn v\u1ECB tr\xED..." })),
1794
- /* @__PURE__ */ React25__default.createElement(SelectContent, null, dropZones.map((zone) => /* @__PURE__ */ React25__default.createElement(SelectItem, { key: zone.id, value: zone.id }, zone.label.replace(/<[^>]*>?/gm, ""))))
1795
- ), showCorrectAnswer && selectedDropZoneId && (isSelectionCorrect ? /* @__PURE__ */ React25__default.createElement(CheckCircle, { className: "h-5 w-5 text-green-500 flex-shrink-0" }) : /* @__PURE__ */ React25__default.createElement(XCircle, { className: "h-5 w-5 text-destructive flex-shrink-0" }))));
1796
- })), showCorrectAnswer && explanation && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-6 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Gi\u1EA3i th\xEDch:"), /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React25__default.createElement("p", { className: "font-semibold text-md" }, "\u0110\xE1p \xE1n \u0111\xFAng:"), /* @__PURE__ */ React25__default.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" }, answerMap.map((map) => {
2490
+ /* @__PURE__ */ React28__default.createElement(SelectTrigger, { id: `select-draggable-${item.id}`, className: "w-full sm:min-w-[200px]" }, /* @__PURE__ */ React28__default.createElement(SelectValue, { placeholder: "Ch\u1ECDn v\u1ECB tr\xED..." })),
2491
+ /* @__PURE__ */ React28__default.createElement(SelectContent, null, dropZones.map((zone) => /* @__PURE__ */ React28__default.createElement(SelectItem, { key: zone.id, value: zone.id }, zone.label.replace(/<[^>]*>?/gm, ""))))
2492
+ ), showCorrectAnswer && selectedDropZoneId && (isSelectionCorrect ? /* @__PURE__ */ React28__default.createElement(CheckCircle, { className: "h-5 w-5 text-green-500 flex-shrink-0" }) : /* @__PURE__ */ React28__default.createElement(XCircle, { className: "h-5 w-5 text-destructive flex-shrink-0" }))));
2493
+ })), showCorrectAnswer && explanation && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-6 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Gi\u1EA3i th\xEDch:"), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React28__default.createElement("p", { className: "font-semibold text-md" }, "\u0110\xE1p \xE1n \u0111\xFAng:"), /* @__PURE__ */ React28__default.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" }, answerMap.map((map) => {
1797
2494
  var _a, _b;
1798
2495
  const draggableText = (_a = draggableItems.find((d) => d.id === map.draggableId)) == null ? void 0 : _a.content;
1799
- const dropZoneText = (_b = dropZones.find((z) => z.id === map.dropZoneId)) == null ? void 0 : _b.label;
1800
- return /* @__PURE__ */ React25__default.createElement("li", { key: map.draggableId }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: `<strong>${draggableText || "N/A"}</strong> v\xE0o <strong>${dropZoneText || "N/A"}</strong>` }));
2496
+ const dropZoneText = (_b = dropZones.find((z3) => z3.id === map.dropZoneId)) == null ? void 0 : _b.label;
2497
+ return /* @__PURE__ */ React28__default.createElement("li", { key: map.draggableId }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: `<strong>${draggableText || "N/A"}</strong> v\xE0o <strong>${dropZoneText || "N/A"}</strong>` }));
1801
2498
  })))));
1802
2499
  };
1803
2500
  var HotspotQuestionUI = ({
@@ -1868,7 +2565,7 @@ var HotspotQuestionUI = ({
1868
2565
  tempDiv.innerHTML = htmlString;
1869
2566
  return tempDiv.textContent || tempDiv.innerText || "";
1870
2567
  };
1871
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "p-0 space-y-4" }, /* @__PURE__ */ React25__default.createElement("div", { className: "relative w-full border border-muted rounded-md overflow-hidden", style: { maxWidth: "100%", maxHeight: "500px" } }, /* @__PURE__ */ React25__default.createElement(
2568
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0 space-y-4" }, /* @__PURE__ */ React28__default.createElement("div", { className: "relative w-full border border-muted rounded-md overflow-hidden", style: { maxWidth: "100%", maxHeight: "500px" } }, /* @__PURE__ */ React28__default.createElement(
1872
2569
  "img",
1873
2570
  {
1874
2571
  src: imageUrl,
@@ -1876,7 +2573,7 @@ var HotspotQuestionUI = ({
1876
2573
  className: "block max-w-full max-h-full object-contain",
1877
2574
  "data-ai-hint": question.imageAltText ? question.imageAltText.split(" ").slice(0, 2).join(" ") : "diagram illustration"
1878
2575
  }
1879
- ), hotspots.map((hotspot) => /* @__PURE__ */ React25__default.createElement(
2576
+ ), hotspots.map((hotspot) => /* @__PURE__ */ React28__default.createElement(
1880
2577
  "div",
1881
2578
  {
1882
2579
  key: hotspot.id,
@@ -1890,9 +2587,9 @@ var HotspotQuestionUI = ({
1890
2587
  if (e.key === "Enter" || e.key === " ") handleHotspotClick(hotspot.id);
1891
2588
  }
1892
2589
  }
1893
- ))), showCorrectAnswer && explanation && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React25__default.createElement("p", { className: "font-semibold text-md" }, "Correct Hotspots:"), /* @__PURE__ */ React25__default.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" }, (correctAnswerIds || []).map((id) => {
2590
+ ))), showCorrectAnswer && explanation && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React28__default.createElement("p", { className: "font-semibold text-md" }, "Correct Hotspots:"), /* @__PURE__ */ React28__default.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" }, (correctAnswerIds || []).map((id) => {
1894
2591
  const hotspot = hotspots.find((h) => h.id === id);
1895
- return /* @__PURE__ */ React25__default.createElement("li", { key: id }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: (hotspot == null ? void 0 : hotspot.description) || (hotspot == null ? void 0 : hotspot.id) || "N/A", className: "inline" }));
2592
+ return /* @__PURE__ */ React28__default.createElement("li", { key: id }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: (hotspot == null ? void 0 : hotspot.description) || (hotspot == null ? void 0 : hotspot.id) || "N/A", className: "inline" }));
1896
2593
  })))));
1897
2594
  };
1898
2595
  var loadScript = (src, async = true) => {
@@ -2010,7 +2707,7 @@ var useBlocklyLoader = () => {
2010
2707
  }, []);
2011
2708
  return { isLoading, loadError, isReady, retry };
2012
2709
  };
2013
- var BlocklyProgrammingQuestionUI = React25__default.forwardRef(({
2710
+ var BlocklyProgrammingQuestionUI = React28__default.forwardRef(({
2014
2711
  question,
2015
2712
  userAnswer,
2016
2713
  showCorrectAnswer = false
@@ -2185,7 +2882,7 @@ var BlocklyProgrammingQuestionUI = React25__default.forwardRef(({
2185
2882
  }, [blocklyReady]);
2186
2883
  const workspaceHeight = showCorrectAnswer ? "300px" : "450px";
2187
2884
  const workspaceContainerId = `blockly-workspace-container-${question.id}`;
2188
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: question.prompt })), question.points && /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", question.points)), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "p-0" }, blocklyLoading && /* @__PURE__ */ React25__default.createElement(
2885
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: question.prompt })), question.points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", question.points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0" }, blocklyLoading && /* @__PURE__ */ React28__default.createElement(
2189
2886
  "div",
2190
2887
  {
2191
2888
  style: {
@@ -2197,8 +2894,8 @@ var BlocklyProgrammingQuestionUI = React25__default.forwardRef(({
2197
2894
  },
2198
2895
  className: "flex items-center justify-center"
2199
2896
  },
2200
- /* @__PURE__ */ React25__default.createElement("div", { className: "text-center" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-muted-foreground animate-pulse mb-2" }, "Loading Blockly Environment..."), /* @__PURE__ */ React25__default.createElement("div", { className: "w-8 h-8 border-4 border-muted border-t-primary rounded-full animate-spin mx-auto" }))
2201
- ), blocklyLoadError && !blocklyLoading && /* @__PURE__ */ React25__default.createElement(
2897
+ /* @__PURE__ */ React28__default.createElement("div", { className: "text-center" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-muted-foreground animate-pulse mb-2" }, "Loading Blockly Environment..."), /* @__PURE__ */ React28__default.createElement("div", { className: "w-8 h-8 border-4 border-muted border-t-primary rounded-full animate-spin mx-auto" }))
2898
+ ), blocklyLoadError && !blocklyLoading && /* @__PURE__ */ React28__default.createElement(
2202
2899
  "div",
2203
2900
  {
2204
2901
  style: {
@@ -2210,14 +2907,14 @@ var BlocklyProgrammingQuestionUI = React25__default.forwardRef(({
2210
2907
  },
2211
2908
  className: "flex items-center justify-center p-4"
2212
2909
  },
2213
- /* @__PURE__ */ React25__default.createElement("div", { className: "text-destructive text-center" }, /* @__PURE__ */ React25__default.createElement("p", { className: "font-semibold text-lg" }, "Failed to load Blockly"), /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm mt-2 mb-3" }, blocklyLoadError), /* @__PURE__ */ React25__default.createElement("div", { className: "space-x-2" }, /* @__PURE__ */ React25__default.createElement(
2910
+ /* @__PURE__ */ React28__default.createElement("div", { className: "text-destructive text-center" }, /* @__PURE__ */ React28__default.createElement("p", { className: "font-semibold text-lg" }, "Failed to load Blockly"), /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm mt-2 mb-3" }, blocklyLoadError), /* @__PURE__ */ React28__default.createElement("div", { className: "space-x-2" }, /* @__PURE__ */ React28__default.createElement(
2214
2911
  "button",
2215
2912
  {
2216
2913
  onClick: retry,
2217
2914
  className: "px-4 py-2 bg-primary text-primary-foreground rounded hover:bg-primary/90 transition-colors text-sm"
2218
2915
  },
2219
2916
  "Try Again"
2220
- ), /* @__PURE__ */ React25__default.createElement(
2917
+ ), /* @__PURE__ */ React28__default.createElement(
2221
2918
  "button",
2222
2919
  {
2223
2920
  onClick: () => window.location.reload(),
@@ -2225,7 +2922,7 @@ var BlocklyProgrammingQuestionUI = React25__default.forwardRef(({
2225
2922
  },
2226
2923
  "Refresh Page"
2227
2924
  )))
2228
- ), !blocklyLoading && !blocklyLoadError && /* @__PURE__ */ React25__default.createElement(
2925
+ ), !blocklyLoading && !blocklyLoadError && /* @__PURE__ */ React28__default.createElement(
2229
2926
  "div",
2230
2927
  {
2231
2928
  id: workspaceContainerId,
@@ -2242,7 +2939,7 @@ var BlocklyProgrammingQuestionUI = React25__default.forwardRef(({
2242
2939
  },
2243
2940
  "aria-label": `Blockly programming workspace for question: ${question.prompt}`
2244
2941
  }
2245
- ), showCorrectAnswer && question.explanation && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React25__default.createElement(MarkdownRenderer, { content: question.explanation, className: "text-sm text-accent-foreground/80" }))));
2942
+ ), showCorrectAnswer && question.explanation && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: question.explanation, className: "text-sm text-accent-foreground/80" }))));
2246
2943
  });
2247
2944
  BlocklyProgrammingQuestionUI.displayName = "BlocklyProgrammingQuestionUI";
2248
2945
  var SCRATCH_JS_ENGINE_LAYOUT_PATH = "/static/scratch-blocks/js/blockly_compressed_vertical.js";
@@ -2523,15 +3220,15 @@ var ScratchProgrammingQuestionUI = forwardRef(({
2523
3220
  }, [isBlocklyReady, initializeWorkspace]);
2524
3221
  const workspaceHeight = showCorrectAnswer ? "300px" : "450px";
2525
3222
  if (isLoadingScripts) {
2526
- return /* @__PURE__ */ React25__default.createElement("div", { style: { height: workspaceHeight, width: "100%", display: "flex", alignItems: "center", justifyContent: "center", border: "1px solid hsl(var(--border))", borderRadius: "0.375rem", backgroundColor: "hsl(var(--background))" } }, /* @__PURE__ */ React25__default.createElement("div", { className: "text-center" }, /* @__PURE__ */ React25__default.createElement("div", { className: "w-8 h-8 border-4 border-muted border-t-primary rounded-full animate-spin mx-auto mb-2" }), /* @__PURE__ */ React25__default.createElement("p", { className: "text-muted-foreground" }, "Loading Scratch Assets...")));
3223
+ return /* @__PURE__ */ React28__default.createElement("div", { style: { height: workspaceHeight, width: "100%", display: "flex", alignItems: "center", justifyContent: "center", border: "1px solid hsl(var(--border))", borderRadius: "0.375rem", backgroundColor: "hsl(var(--background))" } }, /* @__PURE__ */ React28__default.createElement("div", { className: "text-center" }, /* @__PURE__ */ React28__default.createElement("div", { className: "w-8 h-8 border-4 border-muted border-t-primary rounded-full animate-spin mx-auto mb-2" }), /* @__PURE__ */ React28__default.createElement("p", { className: "text-muted-foreground" }, "Loading Scratch Assets...")));
2527
3224
  }
2528
3225
  if (componentError) {
2529
- return /* @__PURE__ */ React25__default.createElement("div", { style: { height: workspaceHeight, width: "100%", color: "hsl(var(--destructive))", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", border: "1px solid hsl(var(--destructive))", borderRadius: "0.375rem", padding: "1rem", backgroundColor: "hsl(var(--card))" } }, /* @__PURE__ */ React25__default.createElement("p", { className: "font-semibold text-lg mb-2" }, "Failed to load Scratch Workspace."), /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm text-muted-foreground mb-3 text-center" }, componentError), /* @__PURE__ */ React25__default.createElement("p", { className: "text-xs text-muted-foreground mb-4 text-center" }, "Please ensure all Scratch/Blockly JavaScript files are correctly copied to your", /* @__PURE__ */ React25__default.createElement("code", null, "public/static/scratch-blocks/js"), " directory. Check browser console for more details.", /* @__PURE__ */ React25__default.createElement("br", null), /* @__PURE__ */ React25__default.createElement("strong", null, "CRITICAL: Ensure you have copied the CSS files from ", /* @__PURE__ */ React25__default.createElement("code", null, "node_modules/scratch-blocks/css/"), " (e.g., ", /* @__PURE__ */ React25__default.createElement("code", null, "vertical.css"), ") to ", /* @__PURE__ */ React25__default.createElement("code", null, "public/static/scratch-blocks/css/"), " and linked it in your main layout. Without CSS, blocks will not render correctly.")), /* @__PURE__ */ React25__default.createElement(Button, { onClick: attemptLoadScripts, variant: "outline" }, "Try Reloading Scripts"));
3226
+ return /* @__PURE__ */ React28__default.createElement("div", { style: { height: workspaceHeight, width: "100%", color: "hsl(var(--destructive))", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", border: "1px solid hsl(var(--destructive))", borderRadius: "0.375rem", padding: "1rem", backgroundColor: "hsl(var(--card))" } }, /* @__PURE__ */ React28__default.createElement("p", { className: "font-semibold text-lg mb-2" }, "Failed to load Scratch Workspace."), /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm text-muted-foreground mb-3 text-center" }, componentError), /* @__PURE__ */ React28__default.createElement("p", { className: "text-xs text-muted-foreground mb-4 text-center" }, "Please ensure all Scratch/Blockly JavaScript files are correctly copied to your", /* @__PURE__ */ React28__default.createElement("code", null, "public/static/scratch-blocks/js"), " directory. Check browser console for more details.", /* @__PURE__ */ React28__default.createElement("br", null), /* @__PURE__ */ React28__default.createElement("strong", null, "CRITICAL: Ensure you have copied the CSS files from ", /* @__PURE__ */ React28__default.createElement("code", null, "node_modules/scratch-blocks/css/"), " (e.g., ", /* @__PURE__ */ React28__default.createElement("code", null, "vertical.css"), ") to ", /* @__PURE__ */ React28__default.createElement("code", null, "public/static/scratch-blocks/css/"), " and linked it in your main layout. Without CSS, blocks will not render correctly.")), /* @__PURE__ */ React28__default.createElement(Button, { onClick: attemptLoadScripts, variant: "outline" }, "Try Reloading Scripts"));
2530
3227
  }
2531
3228
  if (!isBlocklyReady && !isLoadingScripts) {
2532
- return /* @__PURE__ */ React25__default.createElement("div", { style: { height: workspaceHeight, width: "100%", display: "flex", alignItems: "center", justifyContent: "center", border: "1px solid hsl(var(--border))", borderRadius: "0.375rem", backgroundColor: "hsl(var(--background))" } }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-muted-foreground" }, "Scratch environment did not initialize (Blockly not ready). Check console for script loading errors."));
3229
+ return /* @__PURE__ */ React28__default.createElement("div", { style: { height: workspaceHeight, width: "100%", display: "flex", alignItems: "center", justifyContent: "center", border: "1px solid hsl(var(--border))", borderRadius: "0.375rem", backgroundColor: "hsl(var(--background))" } }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-muted-foreground" }, "Scratch environment did not initialize (Blockly not ready). Check console for script loading errors."));
2533
3230
  }
2534
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, question.prompt), question.points && /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", question.points)), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__default.createElement(
3231
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, question.prompt), question.points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", question.points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__default.createElement(
2535
3232
  "div",
2536
3233
  {
2537
3234
  ref: blocklyDivRef,
@@ -2547,17 +3244,237 @@ var ScratchProgrammingQuestionUI = forwardRef(({
2547
3244
  },
2548
3245
  "aria-label": `Scratch programming workspace for question: ${question.prompt}`
2549
3246
  }
2550
- ), showCorrectAnswer && question.explanation && /* @__PURE__ */ React25__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm text-accent-foreground/80" }, question.explanation))));
3247
+ ), showCorrectAnswer && question.explanation && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm text-accent-foreground/80" }, question.explanation))));
2551
3248
  });
2552
3249
  ScratchProgrammingQuestionUI.displayName = "ScratchProgrammingQuestionUI";
3250
+ var Tabs = TabsPrimitive.Root;
3251
+ var TabsList = React28.forwardRef((_a, ref) => {
3252
+ var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
3253
+ return /* @__PURE__ */ React28.createElement(
3254
+ TabsPrimitive.List,
3255
+ __spreadValues({
3256
+ ref,
3257
+ className: cn(
3258
+ "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
3259
+ className
3260
+ )
3261
+ }, props)
3262
+ );
3263
+ });
3264
+ TabsList.displayName = TabsPrimitive.List.displayName;
3265
+ var TabsTrigger = React28.forwardRef((_a, ref) => {
3266
+ var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
3267
+ return /* @__PURE__ */ React28.createElement(
3268
+ TabsPrimitive.Trigger,
3269
+ __spreadValues({
3270
+ ref,
3271
+ className: cn(
3272
+ "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
3273
+ className
3274
+ )
3275
+ }, props)
3276
+ );
3277
+ });
3278
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
3279
+ var TabsContent = React28.forwardRef((_a, ref) => {
3280
+ var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
3281
+ return /* @__PURE__ */ React28.createElement(
3282
+ TabsPrimitive.Content,
3283
+ __spreadValues({
3284
+ ref,
3285
+ className: cn(
3286
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
3287
+ className
3288
+ )
3289
+ }, props)
3290
+ );
3291
+ });
3292
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
3293
+ var TOAST_LIMIT = 1;
3294
+ var TOAST_REMOVE_DELAY = 1e6;
3295
+ var count = 0;
3296
+ function genId() {
3297
+ count = (count + 1) % Number.MAX_SAFE_INTEGER;
3298
+ return count.toString();
3299
+ }
3300
+ var toastTimeouts = /* @__PURE__ */ new Map();
3301
+ var addToRemoveQueue = (toastId) => {
3302
+ if (toastTimeouts.has(toastId)) {
3303
+ return;
3304
+ }
3305
+ const timeout = setTimeout(() => {
3306
+ toastTimeouts.delete(toastId);
3307
+ dispatch({
3308
+ type: "REMOVE_TOAST",
3309
+ toastId
3310
+ });
3311
+ }, TOAST_REMOVE_DELAY);
3312
+ toastTimeouts.set(toastId, timeout);
3313
+ };
3314
+ var reducer = (state, action) => {
3315
+ switch (action.type) {
3316
+ case "ADD_TOAST":
3317
+ return __spreadProps(__spreadValues({}, state), {
3318
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT)
3319
+ });
3320
+ case "UPDATE_TOAST":
3321
+ return __spreadProps(__spreadValues({}, state), {
3322
+ toasts: state.toasts.map(
3323
+ (t) => t.id === action.toast.id ? __spreadValues(__spreadValues({}, t), action.toast) : t
3324
+ )
3325
+ });
3326
+ case "DISMISS_TOAST": {
3327
+ const { toastId } = action;
3328
+ if (toastId) {
3329
+ addToRemoveQueue(toastId);
3330
+ } else {
3331
+ state.toasts.forEach((toast2) => {
3332
+ addToRemoveQueue(toast2.id);
3333
+ });
3334
+ }
3335
+ return __spreadProps(__spreadValues({}, state), {
3336
+ toasts: state.toasts.map(
3337
+ (t) => t.id === toastId || toastId === void 0 ? __spreadProps(__spreadValues({}, t), {
3338
+ open: false
3339
+ }) : t
3340
+ )
3341
+ });
3342
+ }
3343
+ case "REMOVE_TOAST":
3344
+ if (action.toastId === void 0) {
3345
+ return __spreadProps(__spreadValues({}, state), {
3346
+ toasts: []
3347
+ });
3348
+ }
3349
+ return __spreadProps(__spreadValues({}, state), {
3350
+ toasts: state.toasts.filter((t) => t.id !== action.toastId)
3351
+ });
3352
+ }
3353
+ };
3354
+ var listeners = [];
3355
+ var memoryState = { toasts: [] };
3356
+ function dispatch(action) {
3357
+ memoryState = reducer(memoryState, action);
3358
+ listeners.forEach((listener) => {
3359
+ listener(memoryState);
3360
+ });
3361
+ }
3362
+ function toast(_a) {
3363
+ var props = __objRest(_a, []);
3364
+ const id = genId();
3365
+ const update = (props2) => dispatch({
3366
+ type: "UPDATE_TOAST",
3367
+ toast: __spreadProps(__spreadValues({}, props2), { id })
3368
+ });
3369
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
3370
+ dispatch({
3371
+ type: "ADD_TOAST",
3372
+ toast: __spreadProps(__spreadValues({}, props), {
3373
+ id,
3374
+ open: true,
3375
+ onOpenChange: (open) => {
3376
+ if (!open) dismiss();
3377
+ }
3378
+ })
3379
+ });
3380
+ return {
3381
+ id,
3382
+ dismiss,
3383
+ update
3384
+ };
3385
+ }
3386
+ function useToast() {
3387
+ const [state, setState] = React28.useState(memoryState);
3388
+ React28.useEffect(() => {
3389
+ listeners.push(setState);
3390
+ return () => {
3391
+ const index = listeners.indexOf(setState);
3392
+ if (index > -1) {
3393
+ listeners.splice(index, 1);
3394
+ }
3395
+ };
3396
+ }, [state]);
3397
+ return __spreadProps(__spreadValues({}, state), {
3398
+ toast,
3399
+ dismiss: (toastId) => dispatch({ type: "DISMISS_TOAST", toastId })
3400
+ });
3401
+ }
2553
3402
 
2554
- // src/react-ui/components/ui/QuestionRenderer.tsx
2555
- var QuestionRenderer = React25__default.forwardRef(({
3403
+ // src/react-ui/components/ui/CodingQuestionUI.tsx
3404
+ var languageMap = {
3405
+ cpp: cpp(),
3406
+ javascript: javascript({ typescript: true }),
3407
+ // Use TS extension for better JS support
3408
+ python: python(),
3409
+ swift: javascript({ typescript: true }),
3410
+ // Fallback for Swift syntax highlighting
3411
+ csharp: cpp()
3412
+ // Fallback for C# syntax highlighting
3413
+ };
3414
+ var CodingQuestionUI = ({
3415
+ question,
3416
+ onAnswerChange,
3417
+ userAnswer,
3418
+ showCorrectAnswer = false
3419
+ }) => {
3420
+ const [code, setCode] = useState("");
3421
+ const [isRunningTests, setIsRunningTests] = useState(false);
3422
+ const [testResults, setTestResults] = useState([]);
3423
+ const { toast: toast2 } = useToast();
3424
+ useEffect(() => {
3425
+ const initialCode = typeof userAnswer === "string" ? userAnswer : question.functionSignature || "";
3426
+ setCode(initialCode);
3427
+ }, [question.id, userAnswer, question.functionSignature]);
3428
+ const handleCodeChange = useCallback((value) => {
3429
+ setCode(value);
3430
+ onAnswerChange(value);
3431
+ }, [onAnswerChange]);
3432
+ const handleRunPublicTests = async () => {
3433
+ setIsRunningTests(true);
3434
+ setTestResults([]);
3435
+ try {
3436
+ const evaluationService = new CodeEvaluationService();
3437
+ const results = await evaluationService.evaluatePublicTestCases(question, code);
3438
+ setTestResults(results);
3439
+ } catch (error) {
3440
+ toast2({
3441
+ title: "Evaluation Error",
3442
+ description: error instanceof Error ? error.message : "An unknown error occurred.",
3443
+ variant: "destructive"
3444
+ });
3445
+ } finally {
3446
+ setIsRunningTests(false);
3447
+ }
3448
+ };
3449
+ const langExtension = languageMap[question.codingLanguage];
3450
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: question.prompt })), question.points && /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", question.points)), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "p-0 space-y-4" }, /* @__PURE__ */ React28__default.createElement("div", { className: "font-mono text-sm border rounded-md overflow-hidden" }, /* @__PURE__ */ React28__default.createElement(
3451
+ CodeMirror,
3452
+ {
3453
+ value: code,
3454
+ height: "300px",
3455
+ extensions: [langExtension],
3456
+ onChange: handleCodeChange,
3457
+ readOnly: showCorrectAnswer,
3458
+ theme: "dark"
3459
+ }
3460
+ )), !showCorrectAnswer && /* @__PURE__ */ React28__default.createElement(Button, { onClick: handleRunPublicTests, disabled: isRunningTests }, isRunningTests ? /* @__PURE__ */ React28__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React28__default.createElement(Play, { className: "mr-2 h-4 w-4" }), isRunningTests ? "Running..." : "Run Public Tests"), /* @__PURE__ */ React28__default.createElement(Tabs, { defaultValue: "tests", className: "w-full" }, /* @__PURE__ */ React28__default.createElement(TabsList, null, /* @__PURE__ */ React28__default.createElement(TabsTrigger, { value: "tests" }, "Test Cases"), showCorrectAnswer && /* @__PURE__ */ React28__default.createElement(TabsTrigger, { value: "solution" }, "Solution")), /* @__PURE__ */ React28__default.createElement(TabsContent, { value: "tests" }, /* @__PURE__ */ React28__default.createElement("div", { className: "space-y-2 p-2 border rounded-md min-h-[100px]" }, testResults.length > 0 ? testResults.map((result, index) => /* @__PURE__ */ React28__default.createElement("div", { key: result.testCaseId, className: "flex items-center text-sm" }, result.passed ? /* @__PURE__ */ React28__default.createElement(CheckCircle, { className: "h-4 w-4 text-green-500 mr-2" }) : /* @__PURE__ */ React28__default.createElement(XCircle, { className: "h-4 w-4 text-destructive mr-2" }), /* @__PURE__ */ React28__default.createElement("span", null, "Test Case #", index + 1, ": ", result.passed ? "Passed" : "Failed"), !result.passed && /* @__PURE__ */ React28__default.createElement("p", { className: "text-xs text-muted-foreground ml-2" }, "- ", result.reasoning))) : /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm text-muted-foreground" }, showCorrectAnswer ? "Final results are shown in the quiz summary." : "Click 'Run Public Tests' to see results."))), showCorrectAnswer && /* @__PURE__ */ React28__default.createElement(TabsContent, { value: "solution" }, /* @__PURE__ */ React28__default.createElement("div", { className: "font-mono text-sm border rounded-md overflow-hidden" }, /* @__PURE__ */ React28__default.createElement(
3461
+ CodeMirror,
3462
+ {
3463
+ value: question.solutionCode,
3464
+ height: "300px",
3465
+ extensions: [langExtension],
3466
+ readOnly: true,
3467
+ theme: "dark"
3468
+ }
3469
+ )))), showCorrectAnswer && question.explanation && /* @__PURE__ */ React28__default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__default.createElement(MarkdownRenderer, { content: question.explanation, className: "text-sm text-accent-foreground/80" }))));
3470
+ };
3471
+ var QuestionRenderer = React28__default.forwardRef(({
2556
3472
  question,
2557
3473
  onAnswerChange,
2558
3474
  userAnswer,
2559
3475
  showCorrectAnswer = false
2560
3476
  }, ref) => {
3477
+ const { t } = useTranslation();
2561
3478
  const commonProps = {
2562
3479
  question,
2563
3480
  onAnswerChange,
@@ -2566,37 +3483,39 @@ var QuestionRenderer = React25__default.forwardRef(({
2566
3483
  };
2567
3484
  switch (question.questionType) {
2568
3485
  case "multiple_choice":
2569
- return /* @__PURE__ */ React25__default.createElement(MultipleChoiceQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3486
+ return /* @__PURE__ */ React28__default.createElement(MultipleChoiceQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2570
3487
  case "true_false":
2571
- return /* @__PURE__ */ React25__default.createElement(TrueFalseQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3488
+ return /* @__PURE__ */ React28__default.createElement(TrueFalseQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2572
3489
  case "multiple_response":
2573
- return /* @__PURE__ */ React25__default.createElement(MultipleResponseQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3490
+ return /* @__PURE__ */ React28__default.createElement(MultipleResponseQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2574
3491
  case "short_answer":
2575
- return /* @__PURE__ */ React25__default.createElement(ShortAnswerQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3492
+ return /* @__PURE__ */ React28__default.createElement(ShortAnswerQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2576
3493
  case "numeric":
2577
- return /* @__PURE__ */ React25__default.createElement(NumericQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3494
+ return /* @__PURE__ */ React28__default.createElement(NumericQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2578
3495
  case "fill_in_the_blanks":
2579
- return /* @__PURE__ */ React25__default.createElement(FillInTheBlanksQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3496
+ return /* @__PURE__ */ React28__default.createElement(FillInTheBlanksQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2580
3497
  case "sequence":
2581
- return /* @__PURE__ */ React25__default.createElement(SequenceQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3498
+ return /* @__PURE__ */ React28__default.createElement(SequenceQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2582
3499
  case "matching":
2583
- return /* @__PURE__ */ React25__default.createElement(MatchingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3500
+ return /* @__PURE__ */ React28__default.createElement(MatchingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2584
3501
  case "drag_and_drop":
2585
- return /* @__PURE__ */ React25__default.createElement(DragAndDropQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3502
+ return /* @__PURE__ */ React28__default.createElement(DragAndDropQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2586
3503
  case "hotspot":
2587
- return /* @__PURE__ */ React25__default.createElement(HotspotQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3504
+ return /* @__PURE__ */ React28__default.createElement(HotspotQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2588
3505
  case "blockly_programming":
2589
- return /* @__PURE__ */ React25__default.createElement(BlocklyProgrammingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question, ref }));
3506
+ return /* @__PURE__ */ React28__default.createElement(BlocklyProgrammingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question, ref }));
2590
3507
  case "scratch_programming":
2591
- return /* @__PURE__ */ React25__default.createElement(ScratchProgrammingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question, ref }));
3508
+ return /* @__PURE__ */ React28__default.createElement(ScratchProgrammingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question, ref }));
3509
+ case "coding":
3510
+ return /* @__PURE__ */ React28__default.createElement(CodingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2592
3511
  default:
2593
- return /* @__PURE__ */ React25__default.createElement("div", { className: "p-4 border border-destructive bg-destructive/10 rounded-md" }, /* @__PURE__ */ React25__default.createElement("p", { className: "font-semibold text-destructive" }, "Unsupported Question Type"), /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm text-muted-foreground" }, "The question type is not currently supported by the renderer."), /* @__PURE__ */ React25__default.createElement("pre", { className: "mt-2 p-2 bg-muted rounded text-xs font-code overflow-x-auto" }, JSON.stringify(question, null, 2)));
3512
+ return /* @__PURE__ */ React28__default.createElement("div", { className: "p-4 border border-destructive bg-destructive/10 rounded-md" }, /* @__PURE__ */ React28__default.createElement("p", { className: "font-semibold text-destructive" }, t("unsupportedQuestionType")), /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm text-muted-foreground" }, t("unsupportedQuestionTypeDescription")), /* @__PURE__ */ React28__default.createElement("pre", { className: "mt-2 p-2 bg-muted rounded text-xs font-code overflow-x-auto" }, JSON.stringify(question, null, 2)));
2594
3513
  }
2595
3514
  });
2596
3515
  QuestionRenderer.displayName = "QuestionRenderer";
2597
- var Progress = React25.forwardRef((_a, ref) => {
3516
+ var Progress = React28.forwardRef((_a, ref) => {
2598
3517
  var _b = _a, { className, value } = _b, props = __objRest(_b, ["className", "value"]);
2599
- return /* @__PURE__ */ React25.createElement(
3518
+ return /* @__PURE__ */ React28.createElement(
2600
3519
  ProgressPrimitive.Root,
2601
3520
  __spreadValues({
2602
3521
  ref,
@@ -2605,7 +3524,7 @@ var Progress = React25.forwardRef((_a, ref) => {
2605
3524
  className
2606
3525
  )
2607
3526
  }, props),
2608
- /* @__PURE__ */ React25.createElement(
3527
+ /* @__PURE__ */ React28.createElement(
2609
3528
  ProgressPrimitive.Indicator,
2610
3529
  {
2611
3530
  className: "h-full w-full flex-1 bg-primary transition-all",
@@ -2616,9 +3535,9 @@ var Progress = React25.forwardRef((_a, ref) => {
2616
3535
  });
2617
3536
  Progress.displayName = ProgressPrimitive.Root.displayName;
2618
3537
  var Accordion = AccordionPrimitive.Root;
2619
- var AccordionItem = React25.forwardRef((_a, ref) => {
3538
+ var AccordionItem = React28.forwardRef((_a, ref) => {
2620
3539
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
2621
- return /* @__PURE__ */ React25.createElement(
3540
+ return /* @__PURE__ */ React28.createElement(
2622
3541
  AccordionPrimitive.Item,
2623
3542
  __spreadValues({
2624
3543
  ref,
@@ -2627,9 +3546,9 @@ var AccordionItem = React25.forwardRef((_a, ref) => {
2627
3546
  );
2628
3547
  });
2629
3548
  AccordionItem.displayName = "AccordionItem";
2630
- var AccordionTrigger = React25.forwardRef((_a, ref) => {
3549
+ var AccordionTrigger = React28.forwardRef((_a, ref) => {
2631
3550
  var _b = _a, { className, children } = _b, props = __objRest(_b, ["className", "children"]);
2632
- return /* @__PURE__ */ React25.createElement(AccordionPrimitive.Header, { className: "flex" }, /* @__PURE__ */ React25.createElement(
3551
+ return /* @__PURE__ */ React28.createElement(AccordionPrimitive.Header, { className: "flex" }, /* @__PURE__ */ React28.createElement(
2633
3552
  AccordionPrimitive.Trigger,
2634
3553
  __spreadValues({
2635
3554
  ref,
@@ -2639,39 +3558,39 @@ var AccordionTrigger = React25.forwardRef((_a, ref) => {
2639
3558
  )
2640
3559
  }, props),
2641
3560
  children,
2642
- /* @__PURE__ */ React25.createElement(ChevronDown, { className: "h-4 w-4 shrink-0 transition-transform duration-200" })
3561
+ /* @__PURE__ */ React28.createElement(ChevronDown, { className: "h-4 w-4 shrink-0 transition-transform duration-200" })
2643
3562
  ));
2644
3563
  });
2645
3564
  AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
2646
- var AccordionContent = React25.forwardRef((_a, ref) => {
3565
+ var AccordionContent = React28.forwardRef((_a, ref) => {
2647
3566
  var _b = _a, { className, children } = _b, props = __objRest(_b, ["className", "children"]);
2648
- return /* @__PURE__ */ React25.createElement(
3567
+ return /* @__PURE__ */ React28.createElement(
2649
3568
  AccordionPrimitive.Content,
2650
3569
  __spreadValues({
2651
3570
  ref,
2652
3571
  className: "overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
2653
3572
  }, props),
2654
- /* @__PURE__ */ React25.createElement("div", { className: cn("pb-4 pt-0", className) }, children)
3573
+ /* @__PURE__ */ React28.createElement("div", { className: cn("pb-4 pt-0", className) }, children)
2655
3574
  );
2656
3575
  });
2657
3576
  AccordionContent.displayName = AccordionPrimitive.Content.displayName;
2658
- var ScrollArea = React25.forwardRef((_a, ref) => {
3577
+ var ScrollArea = React28.forwardRef((_a, ref) => {
2659
3578
  var _b = _a, { className, children } = _b, props = __objRest(_b, ["className", "children"]);
2660
- return /* @__PURE__ */ React25.createElement(
3579
+ return /* @__PURE__ */ React28.createElement(
2661
3580
  ScrollAreaPrimitive.Root,
2662
3581
  __spreadValues({
2663
3582
  ref,
2664
3583
  className: cn("relative overflow-hidden", className)
2665
3584
  }, props),
2666
- /* @__PURE__ */ React25.createElement(ScrollAreaPrimitive.Viewport, { className: "h-full w-full rounded-[inherit]" }, children),
2667
- /* @__PURE__ */ React25.createElement(ScrollBar, null),
2668
- /* @__PURE__ */ React25.createElement(ScrollAreaPrimitive.Corner, null)
3585
+ /* @__PURE__ */ React28.createElement(ScrollAreaPrimitive.Viewport, { className: "h-full w-full rounded-[inherit]" }, children),
3586
+ /* @__PURE__ */ React28.createElement(ScrollBar, null),
3587
+ /* @__PURE__ */ React28.createElement(ScrollAreaPrimitive.Corner, null)
2669
3588
  );
2670
3589
  });
2671
3590
  ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
2672
- var ScrollBar = React25.forwardRef((_a, ref) => {
3591
+ var ScrollBar = React28.forwardRef((_a, ref) => {
2673
3592
  var _b = _a, { className, orientation = "vertical" } = _b, props = __objRest(_b, ["className", "orientation"]);
2674
- return /* @__PURE__ */ React25.createElement(
3593
+ return /* @__PURE__ */ React28.createElement(
2675
3594
  ScrollAreaPrimitive.ScrollAreaScrollbar,
2676
3595
  __spreadValues({
2677
3596
  ref,
@@ -2683,25 +3602,52 @@ var ScrollBar = React25.forwardRef((_a, ref) => {
2683
3602
  className
2684
3603
  )
2685
3604
  }, props),
2686
- /* @__PURE__ */ React25.createElement(ScrollAreaPrimitive.ScrollAreaThumb, { className: "relative flex-1 rounded-full bg-border" })
3605
+ /* @__PURE__ */ React28.createElement(ScrollAreaPrimitive.ScrollAreaThumb, { className: "relative flex-1 rounded-full bg-border" })
2687
3606
  );
2688
3607
  });
2689
3608
  ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
2690
3609
 
2691
3610
  // src/react-ui/components/ui/QuizResult.tsx
2692
- var QuizResult = ({ result, quizTitle, onExitQuiz }) => {
3611
+ var QuizResult = ({
3612
+ result,
3613
+ quizTitle,
3614
+ onExitQuiz,
3615
+ onGenerateReview,
3616
+ showReviewButton = false,
3617
+ isReviewLoading = false
3618
+ }) => {
2693
3619
  var _a, _b, _c, _d;
3620
+ const { t } = useTranslation();
2694
3621
  const getAnswerDisplay = (answer) => {
2695
- if (answer === null || answer === void 0) return "Not Answered";
3622
+ if (answer === null || answer === void 0) return t("practiceFlow.results.notAnswered");
2696
3623
  if (typeof answer === "boolean") return answer ? "True" : "False";
2697
3624
  if (Array.isArray(answer)) return answer.join(", ");
2698
- if (typeof answer === "object") return JSON.stringify(answer);
3625
+ if (typeof answer === "object") {
3626
+ if (answer.hasOwnProperty("value")) {
3627
+ const value = answer.value;
3628
+ if (value === null || value === void 0) return t("practiceFlow.results.notAnswered");
3629
+ if (typeof value === "boolean") return value ? "True" : "False";
3630
+ if (Array.isArray(value)) return value.join(", ");
3631
+ if (typeof value === "object") return JSON.stringify(value, null, 2);
3632
+ return String(value);
3633
+ }
3634
+ return JSON.stringify(answer, null, 2);
3635
+ }
2699
3636
  return String(answer);
2700
3637
  };
2701
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full max-w-3xl mx-auto shadow-xl" }, /* @__PURE__ */ React25__default.createElement(CardHeader, null, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-3xl font-headline text-center" }, "Quiz Results: ", quizTitle), /* @__PURE__ */ React25__default.createElement(CardDescription, { className: "text-center text-lg" }, "Here's how you performed!")), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "space-y-6" }, /* @__PURE__ */ React25__default.createElement(Card, { className: "bg-secondary/50" }, /* @__PURE__ */ React25__default.createElement(CardHeader, null, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-xl flex items-center" }, /* @__PURE__ */ React25__default.createElement(BarChart2, { className: "mr-2 h-5 w-5 text-primary" }), "Overall Score")), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "grid grid-cols-1 md:grid-cols-3 gap-4 text-center" }, /* @__PURE__ */ React25__default.createElement("div", null, /* @__PURE__ */ React25__default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.score, " / ", result.maxScore), /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm text-muted-foreground" }, "Points")), /* @__PURE__ */ React25__default.createElement("div", null, /* @__PURE__ */ React25__default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.percentage.toFixed(2), "%"), /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm text-muted-foreground" }, "Percentage")), /* @__PURE__ */ React25__default.createElement("div", null, result.passed !== void 0 && (result.passed ? /* @__PURE__ */ React25__default.createElement("div", { className: "flex flex-col items-center text-green-600" }, /* @__PURE__ */ React25__default.createElement(CheckCircle, { className: "h-10 w-10" }), /* @__PURE__ */ React25__default.createElement("p", { className: "text-xl font-semibold mt-1" }, "Passed")) : /* @__PURE__ */ React25__default.createElement("div", { className: "flex flex-col items-center text-destructive" }, /* @__PURE__ */ React25__default.createElement(XCircle, { className: "h-10 w-10" }), /* @__PURE__ */ React25__default.createElement("p", { className: "text-xl font-semibold mt-1" }, "Failed")))))), /* @__PURE__ */ React25__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm" }, /* @__PURE__ */ React25__default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React25__default.createElement(Clock, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React25__default.createElement("span", null, "Total time spent:"), /* @__PURE__ */ React25__default.createElement("span", { className: "font-semibold" }, (_b = (_a = result.totalTimeSpentSeconds) == null ? void 0 : _a.toFixed(0)) != null ? _b : "N/A", " seconds")), /* @__PURE__ */ React25__default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React25__default.createElement(Percent, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React25__default.createElement("span", null, "Average time per question:"), /* @__PURE__ */ React25__default.createElement("span", { className: "font-semibold" }, (_d = (_c = result.averageTimePerQuestionSeconds) == null ? void 0 : _c.toFixed(1)) != null ? _d : "N/A", " seconds"))), result.scormStatus && result.scormStatus !== "idle" && result.scormStatus !== "no_api" && /* @__PURE__ */ React25__default.createElement(Card, null, /* @__PURE__ */ React25__default.createElement(CardHeader, null, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-lg" }, "SCORM Sync Status")), /* @__PURE__ */ React25__default.createElement(CardContent, null, /* @__PURE__ */ React25__default.createElement("p", { className: `flex items-center ${result.scormStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.scormStatus === "error" && /* @__PURE__ */ React25__default.createElement(AlertTriangle, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React25__default.createElement("span", { className: "font-semibold ml-1" }, result.scormStatus)), result.scormError && /* @__PURE__ */ React25__default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.scormError))), result.webhookStatus && result.webhookStatus !== "idle" && /* @__PURE__ */ React25__default.createElement(Card, null, /* @__PURE__ */ React25__default.createElement(CardHeader, null, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-lg" }, "Webhook Sync Status")), /* @__PURE__ */ React25__default.createElement(CardContent, null, /* @__PURE__ */ React25__default.createElement("p", { className: `flex items-center ${result.webhookStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.webhookStatus === "error" && /* @__PURE__ */ React25__default.createElement(AlertTriangle, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React25__default.createElement("span", { className: "font-semibold ml-1" }, result.webhookStatus)), result.webhookError && /* @__PURE__ */ React25__default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.webhookError))), /* @__PURE__ */ React25__default.createElement(Accordion, { type: "single", collapsible: true, className: "w-full" }, /* @__PURE__ */ React25__default.createElement(AccordionItem, { value: "question-breakdown" }, /* @__PURE__ */ React25__default.createElement(AccordionTrigger, { className: "text-lg font-semibold" }, "Detailed Question Breakdown"), /* @__PURE__ */ React25__default.createElement(AccordionContent, null, /* @__PURE__ */ React25__default.createElement(ScrollArea, { className: "h-[300px] pr-4" }, /* @__PURE__ */ React25__default.createElement("ul", { className: "space-y-4" }, result.questionResults.map((qResult, index) => {
3638
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full max-w-3xl mx-auto shadow-xl" }, /* @__PURE__ */ React28__default.createElement(CardHeader, null, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-3xl font-headline text-center" }, t("practiceFlow.results.title", { quizTitle })), /* @__PURE__ */ React28__default.createElement(CardDescription, { className: "text-center text-lg" }, t("practiceFlow.results.description"))), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "space-y-6" }, /* @__PURE__ */ React28__default.createElement(Card, { className: "bg-secondary/50" }, /* @__PURE__ */ React28__default.createElement(CardHeader, null, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-xl flex items-center" }, /* @__PURE__ */ React28__default.createElement(BarChart2, { className: "mr-2 h-5 w-5 text-primary" }), t("practiceFlow.results.overallScore"))), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "grid grid-cols-1 md:grid-cols-3 gap-4 text-center" }, /* @__PURE__ */ React28__default.createElement("div", null, /* @__PURE__ */ React28__default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.score, " / ", result.maxScore), /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm text-muted-foreground" }, t("practiceFlow.results.points"))), /* @__PURE__ */ React28__default.createElement("div", null, /* @__PURE__ */ React28__default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.percentage.toFixed(2), "%"), /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm text-muted-foreground" }, t("practiceFlow.results.percentage"))), /* @__PURE__ */ React28__default.createElement("div", null, result.passed !== void 0 && (result.passed ? /* @__PURE__ */ React28__default.createElement("div", { className: "flex flex-col items-center text-green-600" }, /* @__PURE__ */ React28__default.createElement(CheckCircle, { className: "h-10 w-10" }), /* @__PURE__ */ React28__default.createElement("p", { className: "text-xl font-semibold mt-1" }, t("practiceFlow.results.passed"))) : /* @__PURE__ */ React28__default.createElement("div", { className: "flex flex-col items-center text-destructive" }, /* @__PURE__ */ React28__default.createElement(XCircle, { className: "h-10 w-10" }), /* @__PURE__ */ React28__default.createElement("p", { className: "text-xl font-semibold mt-1" }, t("practiceFlow.results.failed"))))))), /* @__PURE__ */ React28__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm" }, /* @__PURE__ */ React28__default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React28__default.createElement(Clock, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React28__default.createElement("span", null, t("practiceFlow.results.timeSpent")), /* @__PURE__ */ React28__default.createElement("span", { className: "font-semibold" }, (_b = (_a = result.totalTimeSpentSeconds) == null ? void 0 : _a.toFixed(0)) != null ? _b : "N/A", " ", t("practiceFlow.results.timeUnit"))), /* @__PURE__ */ React28__default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React28__default.createElement(Percent, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React28__default.createElement("span", null, t("practiceFlow.results.avgTimePerQuestion")), /* @__PURE__ */ React28__default.createElement("span", { className: "font-semibold" }, (_d = (_c = result.averageTimePerQuestionSeconds) == null ? void 0 : _c.toFixed(1)) != null ? _d : "N/A", " ", t("practiceFlow.results.timeUnit")))), result.scormStatus && result.scormStatus !== "idle" && result.scormStatus !== "no_api" && /* @__PURE__ */ React28__default.createElement(Card, null, /* @__PURE__ */ React28__default.createElement(CardHeader, null, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-lg" }, "SCORM Sync Status")), /* @__PURE__ */ React28__default.createElement(CardContent, null, /* @__PURE__ */ React28__default.createElement("p", { className: `flex items-center ${result.scormStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.scormStatus === "error" && /* @__PURE__ */ React28__default.createElement(AlertTriangle, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React28__default.createElement("span", { className: "font-semibold ml-1" }, result.scormStatus)), result.scormError && /* @__PURE__ */ React28__default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.scormError))), result.webhookStatus && result.webhookStatus !== "idle" && /* @__PURE__ */ React28__default.createElement(Card, null, /* @__PURE__ */ React28__default.createElement(CardHeader, null, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-lg" }, "Webhook Sync Status")), /* @__PURE__ */ React28__default.createElement(CardContent, null, /* @__PURE__ */ React28__default.createElement("p", { className: `flex items-center ${result.webhookStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.webhookStatus === "error" && /* @__PURE__ */ React28__default.createElement(AlertTriangle, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React28__default.createElement("span", { className: "font-semibold ml-1" }, result.webhookStatus)), result.webhookError && /* @__PURE__ */ React28__default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.webhookError))), /* @__PURE__ */ React28__default.createElement(Accordion, { type: "single", collapsible: true, className: "w-full" }, /* @__PURE__ */ React28__default.createElement(AccordionItem, { value: "question-breakdown" }, /* @__PURE__ */ React28__default.createElement(AccordionTrigger, { className: "text-lg font-semibold" }, t("practiceFlow.results.questionBreakdown")), /* @__PURE__ */ React28__default.createElement(AccordionContent, null, /* @__PURE__ */ React28__default.createElement(ScrollArea, { className: "h-[300px] pr-4" }, /* @__PURE__ */ React28__default.createElement("ul", { className: "space-y-4" }, result.questionResults.map((qResult, index) => {
2702
3639
  var _a2, _b2;
2703
- return /* @__PURE__ */ React25__default.createElement("li", { key: qResult.questionId, className: "p-4 border rounded-md bg-background" }, /* @__PURE__ */ React25__default.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React25__default.createElement("h4", { className: "font-semibold" }, "Question ", index + 1), qResult.isCorrect ? /* @__PURE__ */ React25__default.createElement("span", { className: "text-green-600 font-medium flex items-center" }, /* @__PURE__ */ React25__default.createElement(CheckCircle, { className: "mr-1 h-4 w-4" }), " Correct") : /* @__PURE__ */ React25__default.createElement("span", { className: "text-destructive font-medium flex items-center" }, /* @__PURE__ */ React25__default.createElement(XCircle, { className: "mr-1 h-4 w-4" }), " Incorrect")), /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React25__default.createElement("span", { className: "font-medium" }, "Your Answer:"), " ", getAnswerDisplay(qResult.userAnswer)), !qResult.isCorrect && /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React25__default.createElement("span", { className: "font-medium" }, "Correct Answer:"), " ", getAnswerDisplay(qResult.correctAnswer)), /* @__PURE__ */ React25__default.createElement("p", { className: "text-xs text-muted-foreground" }, /* @__PURE__ */ React25__default.createElement("span", { className: "font-medium" }, "Points Earned:"), " ", qResult.pointsEarned), /* @__PURE__ */ React25__default.createElement("p", { className: "text-xs text-muted-foreground" }, /* @__PURE__ */ React25__default.createElement("span", { className: "font-medium" }, "Time Spent:"), " ", (_b2 = (_a2 = qResult.timeSpentSeconds) == null ? void 0 : _a2.toFixed(0)) != null ? _b2 : "N/A", "s"));
2704
- }))))))), /* @__PURE__ */ React25__default.createElement(CardFooter, null, onExitQuiz && /* @__PURE__ */ React25__default.createElement(Button, { variant: "outline", onClick: onExitQuiz, className: "w-full" }, /* @__PURE__ */ React25__default.createElement(LogOut, { className: "mr-2 h-4 w-4" }), "Exit Results")));
3640
+ return /* @__PURE__ */ React28__default.createElement("li", { key: qResult.questionId, className: "p-4 border rounded-md bg-background" }, /* @__PURE__ */ React28__default.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React28__default.createElement("h4", { className: "font-semibold" }, t("common.questions", { count: index + 1 })), qResult.isCorrect ? /* @__PURE__ */ React28__default.createElement("span", { className: "text-green-600 font-medium flex items-center" }, /* @__PURE__ */ React28__default.createElement(CheckCircle, { className: "mr-1 h-4 w-4" }), " ", t("practiceFlow.results.passed")) : /* @__PURE__ */ React28__default.createElement("span", { className: "text-destructive font-medium flex items-center" }, /* @__PURE__ */ React28__default.createElement(XCircle, { className: "mr-1 h-4 w-4" }), " ", t("practiceFlow.results.failed"))), /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React28__default.createElement("span", { className: "font-medium" }, t("practiceFlow.results.yourAnswer")), " ", getAnswerDisplay(qResult.userAnswer)), !qResult.isCorrect && /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React28__default.createElement("span", { className: "font-medium" }, t("practiceFlow.results.correctAnswer")), " ", getAnswerDisplay(qResult.correctAnswer)), /* @__PURE__ */ React28__default.createElement("p", { className: "text-xs text-muted-foreground" }, /* @__PURE__ */ React28__default.createElement("span", { className: "font-medium" }, t("practiceFlow.results.pointsEarned")), " ", qResult.pointsEarned), /* @__PURE__ */ React28__default.createElement("p", { className: "text-xs text-muted-foreground" }, /* @__PURE__ */ React28__default.createElement("span", { className: "font-medium" }, t("practiceFlow.results.timeSpent")), " ", (_b2 = (_a2 = qResult.timeSpentSeconds) == null ? void 0 : _a2.toFixed(0)) != null ? _b2 : "N/A", t("practiceFlow.results.timeUnit")));
3641
+ }))))))), /* @__PURE__ */ React28__default.createElement(CardFooter, { className: "flex flex-col sm:flex-row justify-between gap-2" }, onExitQuiz && /* @__PURE__ */ React28__default.createElement(Button, { variant: "outline", onClick: onExitQuiz, className: "w-full sm:w-auto" }, /* @__PURE__ */ React28__default.createElement(LogOut, { className: "mr-2 h-4 w-4" }), t("common.exit")), showReviewButton && onGenerateReview && /* @__PURE__ */ React28__default.createElement(
3642
+ Button,
3643
+ {
3644
+ onClick: onGenerateReview,
3645
+ disabled: isReviewLoading,
3646
+ className: "w-full sm:w-auto"
3647
+ },
3648
+ isReviewLoading ? /* @__PURE__ */ React28__default.createElement(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React28__default.createElement(Wand2, { className: "mr-2 h-4 w-4" }),
3649
+ isReviewLoading ? t("practiceFlow.results.generatingReview") : t("practiceFlow.results.generateReview")
3650
+ )));
2705
3651
  };
2706
3652
 
2707
3653
  // src/react-ui/components/ui/QuizPlayer.tsx
@@ -2717,49 +3663,65 @@ var QuizPlayer = ({ quizConfig, onQuizComplete, onExitQuiz }) => {
2717
3663
  const [timeLeft, setTimeLeft] = useState(null);
2718
3664
  const [isLoading, setIsLoading] = useState(true);
2719
3665
  const [error, setError] = useState(null);
3666
+ const { t } = useTranslation();
2720
3667
  const programmingQuestionRef = useRef(null);
3668
+ const engineRef = useRef(null);
3669
+ const isInitializedRef = useRef(false);
3670
+ const callbacks = useMemo(() => ({
3671
+ onQuizStart: (initialData) => {
3672
+ setCurrentQuestion(initialData.initialQuestion);
3673
+ setCurrentQuestionNumber(initialData.currentQuestionNumber);
3674
+ setTotalQuestions(initialData.totalQuestions);
3675
+ setTimeLeft(initialData.timeLimitInSeconds);
3676
+ setIsLoading(false);
3677
+ },
3678
+ onQuestionChange: (question, qNum, total) => {
3679
+ var _a2;
3680
+ setCurrentQuestion(question);
3681
+ setCurrentQuestionNumber(qNum);
3682
+ setTotalQuestions(total);
3683
+ const existingAnswer = (_a2 = engineRef.current) == null ? void 0 : _a2.getUserAnswer((question == null ? void 0 : question.id) || "");
3684
+ setUserAnswer(existingAnswer !== void 0 ? existingAnswer : null);
3685
+ },
3686
+ onQuizFinish: (results) => {
3687
+ setFinalResult(results);
3688
+ setQuizFinished(true);
3689
+ onQuizComplete(results);
3690
+ setIsLoading(false);
3691
+ },
3692
+ onTimeTick: (timeLeftInSeconds) => {
3693
+ setTimeLeft(timeLeftInSeconds);
3694
+ },
3695
+ onQuizTimeUp: () => {
3696
+ setError("Time's up! Your quiz has been submitted automatically.");
3697
+ }
3698
+ }), [onQuizComplete]);
2721
3699
  const handleAnswerChange = useCallback((answer) => {
2722
3700
  if ((currentQuestion == null ? void 0 : currentQuestion.questionType) !== "blockly_programming" && (currentQuestion == null ? void 0 : currentQuestion.questionType) !== "scratch_programming") {
2723
3701
  setUserAnswer(answer);
2724
3702
  }
2725
- }, [currentQuestion]);
3703
+ }, [currentQuestion == null ? void 0 : currentQuestion.questionType]);
3704
+ const quizConfigKey = useMemo(() => {
3705
+ return JSON.stringify({
3706
+ id: quizConfig.id,
3707
+ version: quizConfig.version,
3708
+ title: quizConfig.title
3709
+ });
3710
+ }, [quizConfig.id, quizConfig.version, quizConfig.title]);
2726
3711
  useEffect(() => {
3712
+ if (isInitializedRef.current) {
3713
+ return;
3714
+ }
2727
3715
  setIsLoading(true);
2728
3716
  setError(null);
2729
3717
  setQuizFinished(false);
2730
3718
  setFinalResult(null);
2731
3719
  setUserAnswer(null);
3720
+ isInitializedRef.current = true;
2732
3721
  let localQuizEngine = null;
2733
3722
  try {
2734
- const callbacks = {
2735
- onQuizStart: (initialData) => {
2736
- setCurrentQuestion(initialData.initialQuestion);
2737
- setCurrentQuestionNumber(initialData.currentQuestionNumber);
2738
- setTotalQuestions(initialData.totalQuestions);
2739
- setTimeLeft(initialData.timeLimitInSeconds);
2740
- setIsLoading(false);
2741
- },
2742
- onQuestionChange: (question, qNum, total) => {
2743
- setCurrentQuestion(question);
2744
- setCurrentQuestionNumber(qNum);
2745
- setTotalQuestions(total);
2746
- const existingAnswer = engine == null ? void 0 : engine.getUserAnswer((question == null ? void 0 : question.id) || "");
2747
- setUserAnswer(existingAnswer !== void 0 ? existingAnswer : null);
2748
- },
2749
- onQuizFinish: (results) => {
2750
- setFinalResult(results);
2751
- setQuizFinished(true);
2752
- onQuizComplete(results);
2753
- setIsLoading(false);
2754
- },
2755
- onTimeTick: (timeLeftInSeconds) => {
2756
- setTimeLeft(timeLeftInSeconds);
2757
- },
2758
- onQuizTimeUp: () => {
2759
- setError("Time's up! Your quiz has been submitted automatically.");
2760
- }
2761
- };
2762
3723
  localQuizEngine = new QuizEngine({ config: quizConfig, callbacks });
3724
+ engineRef.current = localQuizEngine;
2763
3725
  setEngine(localQuizEngine);
2764
3726
  const initialQ = localQuizEngine.getCurrentQuestion();
2765
3727
  setCurrentQuestion(initialQ);
@@ -2771,84 +3733,88 @@ var QuizPlayer = ({ quizConfig, onQuizComplete, onExitQuiz }) => {
2771
3733
  setUserAnswer(existingAnswer !== void 0 ? existingAnswer : null);
2772
3734
  }
2773
3735
  setIsLoading(false);
2774
- return () => {
2775
- if (localQuizEngine) {
2776
- localQuizEngine.destroy();
2777
- }
2778
- };
2779
3736
  } catch (e) {
2780
- console.error("Error initializing QuizEngine:", e);
2781
3737
  setError(e instanceof Error ? e.message : "Failed to load quiz.");
2782
3738
  setIsLoading(false);
2783
- if (localQuizEngine) {
2784
- localQuizEngine.destroy();
2785
- }
3739
+ isInitializedRef.current = false;
2786
3740
  }
2787
- }, [quizConfig, onQuizComplete]);
2788
- const handleSubmitAnswer = () => {
3741
+ return () => {
3742
+ if (localQuizEngine) localQuizEngine.destroy();
3743
+ if (engineRef.current) engineRef.current = null;
3744
+ isInitializedRef.current = false;
3745
+ };
3746
+ }, [quizConfigKey, callbacks, quizConfig]);
3747
+ const handleSubmitAnswer = useCallback(() => {
2789
3748
  var _a2;
2790
- if (!engine || !currentQuestion) return;
3749
+ const currentEngine = engineRef.current;
3750
+ if (!currentEngine || !currentQuestion) return;
2791
3751
  let answerToSubmit = null;
2792
3752
  if (currentQuestion.questionType === "blockly_programming" || currentQuestion.questionType === "scratch_programming") {
2793
3753
  if (programmingQuestionRef.current && typeof programmingQuestionRef.current.getWorkspaceXml === "function") {
2794
3754
  answerToSubmit = programmingQuestionRef.current.getWorkspaceXml();
2795
3755
  } else {
2796
- console.warn(`${currentQuestion.questionType} ref not available for submission. Submitting last known answer from engine if any.`);
2797
- answerToSubmit = (_a2 = engine.getUserAnswer(currentQuestion.id)) != null ? _a2 : null;
3756
+ answerToSubmit = (_a2 = currentEngine.getUserAnswer(currentQuestion.id)) != null ? _a2 : null;
2798
3757
  }
2799
3758
  } else {
2800
3759
  answerToSubmit = userAnswer;
2801
3760
  }
2802
3761
  if (answerToSubmit !== void 0) {
2803
- engine.submitAnswer(currentQuestion.id, answerToSubmit);
2804
- }
2805
- };
2806
- const handleNext = () => {
2807
- if (engine) {
2808
- handleSubmitAnswer();
2809
- if (engine.getCurrentQuestionNumber() < engine.getTotalQuestions()) {
2810
- engine.nextQuestion();
2811
- } else {
2812
- handleFinishQuiz();
2813
- }
2814
- }
2815
- };
2816
- const handlePrevious = () => {
2817
- if (engine && engine.getCurrentQuestionNumber() > 1) {
2818
- handleSubmitAnswer();
2819
- engine.previousQuestion();
2820
- }
2821
- };
2822
- const handleFinishQuiz = async () => {
2823
- if (engine) {
2824
- setIsLoading(true);
2825
- handleSubmitAnswer();
2826
- await engine.calculateResults();
2827
- }
2828
- };
3762
+ currentEngine.submitAnswer(currentQuestion.id, answerToSubmit);
3763
+ }
3764
+ }, [currentQuestion, userAnswer]);
3765
+ const handleNext = useCallback(() => {
3766
+ const currentEngine = engineRef.current;
3767
+ if (!currentEngine) return;
3768
+ handleSubmitAnswer();
3769
+ if (currentEngine.getCurrentQuestionNumber() < currentEngine.getTotalQuestions()) {
3770
+ currentEngine.nextQuestion();
3771
+ } else {
3772
+ handleFinishQuiz();
3773
+ }
3774
+ }, [handleSubmitAnswer]);
3775
+ const handlePrevious = useCallback(() => {
3776
+ const currentEngine = engineRef.current;
3777
+ if (!currentEngine || currentEngine.getCurrentQuestionNumber() <= 1) return;
3778
+ handleSubmitAnswer();
3779
+ currentEngine.previousQuestion();
3780
+ }, [handleSubmitAnswer]);
3781
+ const handleFinishQuiz = useCallback(async () => {
3782
+ const currentEngine = engineRef.current;
3783
+ if (!currentEngine) return;
3784
+ setIsLoading(true);
3785
+ handleSubmitAnswer();
3786
+ await currentEngine.calculateResults();
3787
+ }, [handleSubmitAnswer]);
2829
3788
  const progressPercent = useMemo(() => {
2830
3789
  if (totalQuestions === 0) return 0;
2831
3790
  return quizFinished ? 100 : Math.max(0, Math.min(100, (currentQuestionNumber - 1) / totalQuestions * 100));
2832
3791
  }, [currentQuestionNumber, totalQuestions, quizFinished]);
3792
+ const formatTime = useCallback((seconds) => {
3793
+ if (seconds === null) return "-:--";
3794
+ const mins = Math.floor(seconds / 60);
3795
+ const secs = seconds % 60;
3796
+ return `${mins}:${secs < 10 ? "0" : ""}${secs}`;
3797
+ }, []);
2833
3798
  if (isLoading) {
2834
- return /* @__PURE__ */ React25__default.createElement("div", { className: "flex flex-col items-center justify-center h-64" }, /* @__PURE__ */ React25__default.createElement(Loader2, { className: "h-12 w-12 animate-spin text-primary" }), /* @__PURE__ */ React25__default.createElement("p", { className: "mt-4 text-muted-foreground" }, "Loading Quiz..."));
3799
+ return /* @__PURE__ */ React28__default.createElement("div", { className: "flex flex-col items-center justify-center h-64" }, /* @__PURE__ */ React28__default.createElement(Loader2, { className: "h-12 w-12 animate-spin text-primary" }), /* @__PURE__ */ React28__default.createElement("p", { className: "mt-4 text-muted-foreground" }, t("common.loading")));
2835
3800
  }
2836
3801
  if (error) {
2837
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full max-w-2xl mx-auto shadow-xl" }, /* @__PURE__ */ React25__default.createElement(CardHeader, null, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-destructive flex items-center" }, /* @__PURE__ */ React25__default.createElement(AlertCircle, { className: "mr-2 h-6 w-6" }), "Quiz Error")), /* @__PURE__ */ React25__default.createElement(CardContent, null, /* @__PURE__ */ React25__default.createElement("p", null, error)), /* @__PURE__ */ React25__default.createElement(CardFooter, null, onExitQuiz && /* @__PURE__ */ React25__default.createElement(Button, { variant: "outline", onClick: onExitQuiz }, /* @__PURE__ */ React25__default.createElement(LogOut, { className: "mr-2 h-4 w-4" }), " Exit Quiz")));
3802
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full max-w-2xl mx-auto shadow-xl" }, /* @__PURE__ */ React28__default.createElement(CardHeader, null, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-destructive flex items-center" }, /* @__PURE__ */ React28__default.createElement(AlertCircle, { className: "mr-2 h-6 w-6" }), "Quiz Error")), /* @__PURE__ */ React28__default.createElement(CardContent, null, /* @__PURE__ */ React28__default.createElement("p", null, error)), /* @__PURE__ */ React28__default.createElement(CardFooter, null, onExitQuiz && /* @__PURE__ */ React28__default.createElement(Button, { variant: "outline", onClick: onExitQuiz }, /* @__PURE__ */ React28__default.createElement(LogOut, { className: "mr-2 h-4 w-4" }), " ", t("common.exit"))));
2838
3803
  }
2839
3804
  if (quizFinished && finalResult) {
2840
- return /* @__PURE__ */ React25__default.createElement(QuizResult, { result: finalResult, onExitQuiz, quizTitle: quizConfig.title });
3805
+ return /* @__PURE__ */ React28__default.createElement(QuizResult, { result: finalResult, onExitQuiz, quizTitle: quizConfig.title });
2841
3806
  }
2842
3807
  if (!currentQuestion) {
2843
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full max-w-2xl mx-auto shadow-xl" }, /* @__PURE__ */ React25__default.createElement(CardHeader, null, /* @__PURE__ */ React25__default.createElement(CardTitle, null, "Quiz Ended"), /* @__PURE__ */ React25__default.createElement(CardDescription, null, "No more questions, or quiz not loaded correctly.")), /* @__PURE__ */ React25__default.createElement(CardFooter, null, onExitQuiz && /* @__PURE__ */ React25__default.createElement(Button, { variant: "outline", onClick: onExitQuiz }, /* @__PURE__ */ React25__default.createElement(LogOut, { className: "mr-2 h-4 w-4" }), " Exit Quiz")));
3808
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full max-w-2xl mx-auto shadow-xl" }, /* @__PURE__ */ React28__default.createElement(CardHeader, null, /* @__PURE__ */ React28__default.createElement(CardTitle, null, "Quiz Ended"), /* @__PURE__ */ React28__default.createElement(CardDescription, null, "No more questions, or quiz not loaded correctly.")), /* @__PURE__ */ React28__default.createElement(CardFooter, null, onExitQuiz && /* @__PURE__ */ React28__default.createElement(Button, { variant: "outline", onClick: onExitQuiz }, /* @__PURE__ */ React28__default.createElement(LogOut, { className: "mr-2 h-4 w-4" }), " ", t("common.exit"))));
2844
3809
  }
2845
- const formatTime = (seconds) => {
2846
- if (seconds === null) return "-:--";
2847
- const mins = Math.floor(seconds / 60);
2848
- const secs = seconds % 60;
2849
- return `${mins}:${secs < 10 ? "0" : ""}${secs}`;
2850
- };
2851
- return /* @__PURE__ */ React25__default.createElement(Card, { className: "w-full max-w-3xl mx-auto shadow-xl" }, /* @__PURE__ */ React25__default.createElement(CardHeader, null, /* @__PURE__ */ React25__default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React25__default.createElement(CardTitle, { className: "text-2xl font-headline" }, quizConfig.title), timeLeft !== null && /* @__PURE__ */ React25__default.createElement("div", { className: "flex items-center text-sm text-muted-foreground" }, /* @__PURE__ */ React25__default.createElement(Clock, { className: "mr-1 h-4 w-4" }), "Time Left: ", formatTime(timeLeft))), quizConfig.description && /* @__PURE__ */ React25__default.createElement(CardDescription, null, quizConfig.description), /* @__PURE__ */ React25__default.createElement("div", { className: "mt-2" }, /* @__PURE__ */ React25__default.createElement(Progress, { value: progressPercent, "aria-label": `Quiz progress: ${currentQuestionNumber} of ${totalQuestions} questions`, className: "w-full" }), /* @__PURE__ */ React25__default.createElement("p", { className: "text-sm text-muted-foreground mt-1 text-right" }, "Question ", currentQuestionNumber, " of ", totalQuestions))), /* @__PURE__ */ React25__default.createElement(CardContent, { className: "min-h-[200px]" }, /* @__PURE__ */ React25__default.createElement(
3810
+ return /* @__PURE__ */ React28__default.createElement(Card, { className: "w-full max-w-3xl mx-auto shadow-xl" }, /* @__PURE__ */ React28__default.createElement(CardHeader, null, /* @__PURE__ */ React28__default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React28__default.createElement(CardTitle, { className: "text-2xl font-headline" }, quizConfig.title), timeLeft !== null && /* @__PURE__ */ React28__default.createElement("div", { className: "flex items-center text-sm text-muted-foreground" }, /* @__PURE__ */ React28__default.createElement(Clock, { className: "mr-1 h-4 w-4" }), t("practiceFlow.player.timeLeft", { time: formatTime(timeLeft) }))), quizConfig.description && /* @__PURE__ */ React28__default.createElement(CardDescription, null, quizConfig.description), /* @__PURE__ */ React28__default.createElement("div", { className: "mt-2" }, /* @__PURE__ */ React28__default.createElement(
3811
+ Progress,
3812
+ {
3813
+ value: progressPercent,
3814
+ "aria-label": `Quiz progress: ${currentQuestionNumber} of ${totalQuestions} questions`,
3815
+ className: "w-full"
3816
+ }
3817
+ ), /* @__PURE__ */ React28__default.createElement("p", { className: "text-sm text-muted-foreground mt-1 text-right" }, t("practiceFlow.player.questionProgress", { current: currentQuestionNumber, total: totalQuestions })))), /* @__PURE__ */ React28__default.createElement(CardContent, { className: "min-h-[200px]" }, /* @__PURE__ */ React28__default.createElement(
2852
3818
  QuestionRenderer,
2853
3819
  {
2854
3820
  question: currentQuestion,
@@ -2858,7 +3824,25 @@ var QuizPlayer = ({ quizConfig, onQuizComplete, onExitQuiz }) => {
2858
3824
  key: currentQuestion.id,
2859
3825
  ref: currentQuestion.questionType === "blockly_programming" || currentQuestion.questionType === "scratch_programming" ? programmingQuestionRef : null
2860
3826
  }
2861
- )), /* @__PURE__ */ React25__default.createElement(CardFooter, { className: "flex justify-between items-center" }, /* @__PURE__ */ React25__default.createElement(Button, { variant: "outline", onClick: handlePrevious, disabled: currentQuestionNumber <= 1 }, /* @__PURE__ */ React25__default.createElement(ChevronLeft, { className: "mr-2 h-4 w-4" }), " Previous"), onExitQuiz && /* @__PURE__ */ React25__default.createElement(Button, { variant: "ghost", onClick: onExitQuiz, className: "text-muted-foreground hover:text-destructive" }, "Exit Quiz"), /* @__PURE__ */ React25__default.createElement(Button, { onClick: handleNext }, currentQuestionNumber === totalQuestions ? "Finish Quiz" : "Next", currentQuestionNumber !== totalQuestions && /* @__PURE__ */ React25__default.createElement(ChevronRight, { className: "ml-2 h-4 w-4" }), currentQuestionNumber === totalQuestions && /* @__PURE__ */ React25__default.createElement(CheckCircle, { className: "ml-2 h-4 w-4" }))));
3827
+ )), /* @__PURE__ */ React28__default.createElement(CardFooter, { className: "flex justify-between items-center" }, /* @__PURE__ */ React28__default.createElement(
3828
+ Button,
3829
+ {
3830
+ variant: "outline",
3831
+ onClick: handlePrevious,
3832
+ disabled: currentQuestionNumber <= 1
3833
+ },
3834
+ /* @__PURE__ */ React28__default.createElement(ChevronLeft, { className: "mr-2 h-4 w-4" }),
3835
+ " ",
3836
+ t("common.previous")
3837
+ ), onExitQuiz && /* @__PURE__ */ React28__default.createElement(
3838
+ Button,
3839
+ {
3840
+ variant: "ghost",
3841
+ onClick: onExitQuiz,
3842
+ className: "text-muted-foreground hover:text-destructive"
3843
+ },
3844
+ t("common.exit")
3845
+ ), /* @__PURE__ */ React28__default.createElement(Button, { onClick: handleNext }, currentQuestionNumber === totalQuestions ? t("practiceFlow.player.finishQuiz") : t("common.next"), currentQuestionNumber !== totalQuestions && /* @__PURE__ */ React28__default.createElement(ChevronRight, { className: "ml-2 h-4 w-4" }), currentQuestionNumber === totalQuestions && /* @__PURE__ */ React28__default.createElement(CheckCircle, { className: "ml-2 h-4 w-4" }))));
2862
3846
  };
2863
3847
 
2864
3848
  // src/player.ts
@@ -2887,20 +3871,20 @@ function mountQuizPlayer(targetElementId, quizConfig) {
2887
3871
  }
2888
3872
  };
2889
3873
  if (quizResult) {
2890
- return React25__default.createElement(QuizResult, {
3874
+ return React28__default.createElement(QuizResult, {
2891
3875
  result: quizResult,
2892
3876
  quizTitle: quizConfig.title,
2893
3877
  onExitQuiz: handleExit
2894
3878
  });
2895
3879
  }
2896
- return React25__default.createElement(QuizPlayer, {
3880
+ return React28__default.createElement(QuizPlayer, {
2897
3881
  quizConfig,
2898
3882
  onQuizComplete: handleQuizComplete,
2899
3883
  onExitQuiz: handleExit
2900
3884
  });
2901
3885
  };
2902
3886
  const root = ReactDOM.createRoot(targetElement);
2903
- root.render(React25__default.createElement(React25__default.StrictMode, null, React25__default.createElement(AppContainer)));
3887
+ root.render(React28__default.createElement(React28__default.StrictMode, null, React28__default.createElement(AppContainer)));
2904
3888
  }
2905
3889
 
2906
3890
  export { mountQuizPlayer };