@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.cjs CHANGED
@@ -1,7 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var React25 = require('react');
3
+ var React28 = require('react');
4
4
  var ReactDOM = require('react-dom/client');
5
+ var reactI18next = require('react-i18next');
6
+ var zod = require('zod');
7
+ var genkit = require('genkit');
8
+ var googleai = require('@genkit-ai/googleai');
5
9
  var RadioGroupPrimitive = require('@radix-ui/react-radio-group');
6
10
  var lucideReact = require('lucide-react');
7
11
  var clsx = require('clsx');
@@ -16,6 +20,11 @@ var rehypeKatex = require('rehype-katex');
16
20
  var CheckboxPrimitive = require('@radix-ui/react-checkbox');
17
21
  var reactSlot = require('@radix-ui/react-slot');
18
22
  var SelectPrimitive = require('@radix-ui/react-select');
23
+ var CodeMirror = require('@uiw/react-codemirror');
24
+ var langCpp = require('@codemirror/lang-cpp');
25
+ var langJavascript = require('@codemirror/lang-javascript');
26
+ var langPython = require('@codemirror/lang-python');
27
+ var TabsPrimitive = require('@radix-ui/react-tabs');
19
28
  var ProgressPrimitive = require('@radix-ui/react-progress');
20
29
  var AccordionPrimitive = require('@radix-ui/react-accordion');
21
30
  var ScrollAreaPrimitive = require('@radix-ui/react-scroll-area');
@@ -40,7 +49,7 @@ function _interopNamespace(e) {
40
49
  return Object.freeze(n);
41
50
  }
42
51
 
43
- var React25__namespace = /*#__PURE__*/_interopNamespace(React25);
52
+ var React28__namespace = /*#__PURE__*/_interopNamespace(React28);
44
53
  var ReactDOM__default = /*#__PURE__*/_interopDefault(ReactDOM);
45
54
  var RadioGroupPrimitive__namespace = /*#__PURE__*/_interopNamespace(RadioGroupPrimitive);
46
55
  var LabelPrimitive__namespace = /*#__PURE__*/_interopNamespace(LabelPrimitive);
@@ -51,6 +60,8 @@ var remarkMath__default = /*#__PURE__*/_interopDefault(remarkMath);
51
60
  var rehypeKatex__default = /*#__PURE__*/_interopDefault(rehypeKatex);
52
61
  var CheckboxPrimitive__namespace = /*#__PURE__*/_interopNamespace(CheckboxPrimitive);
53
62
  var SelectPrimitive__namespace = /*#__PURE__*/_interopNamespace(SelectPrimitive);
63
+ var CodeMirror__default = /*#__PURE__*/_interopDefault(CodeMirror);
64
+ var TabsPrimitive__namespace = /*#__PURE__*/_interopNamespace(TabsPrimitive);
54
65
  var ProgressPrimitive__namespace = /*#__PURE__*/_interopNamespace(ProgressPrimitive);
55
66
  var AccordionPrimitive__namespace = /*#__PURE__*/_interopNamespace(AccordionPrimitive);
56
67
  var ScrollAreaPrimitive__namespace = /*#__PURE__*/_interopNamespace(ScrollAreaPrimitive);
@@ -334,6 +345,847 @@ var SCORMService = class {
334
345
  }
335
346
  };
336
347
 
348
+ // src/services/evaluators/multiple-choice-evaluator.ts
349
+ var MultipleChoiceEvaluator = class {
350
+ async evaluate(question, answer) {
351
+ var _a;
352
+ const points = (_a = question.points) != null ? _a : 0;
353
+ const correctAnswerId = question.correctAnswerId;
354
+ const isCorrect = answer === correctAnswerId;
355
+ const correctOption = question.options.find((opt) => opt.id === correctAnswerId);
356
+ const correctAnswerDetail = {
357
+ id: correctAnswerId,
358
+ value: (correctOption == null ? void 0 : correctOption.text) || ""
359
+ };
360
+ return Promise.resolve({
361
+ isCorrect,
362
+ correctAnswer: correctAnswerDetail,
363
+ pointsEarned: isCorrect ? points : 0
364
+ });
365
+ }
366
+ };
367
+
368
+ // src/services/evaluators/multiple-response-evaluator.ts
369
+ var MultipleResponseEvaluator = class {
370
+ async evaluate(question, answer) {
371
+ var _a;
372
+ const points = (_a = question.points) != null ? _a : 0;
373
+ const correctAnswerIds = question.correctAnswerIds;
374
+ let isCorrect = false;
375
+ if (Array.isArray(answer)) {
376
+ const userAnswerSet = new Set(answer);
377
+ const correctAnswerSet = new Set(correctAnswerIds);
378
+ isCorrect = userAnswerSet.size === correctAnswerSet.size && [...userAnswerSet].every((id) => correctAnswerSet.has(id));
379
+ }
380
+ const correctValues = correctAnswerIds.map(
381
+ (id) => {
382
+ var _a2;
383
+ return ((_a2 = question.options.find((opt) => opt.id === id)) == null ? void 0 : _a2.text) || "";
384
+ }
385
+ );
386
+ const correctAnswerDetail = {
387
+ id: correctAnswerIds,
388
+ value: correctValues
389
+ };
390
+ return Promise.resolve({
391
+ isCorrect,
392
+ correctAnswer: correctAnswerDetail,
393
+ pointsEarned: isCorrect ? points : 0
394
+ });
395
+ }
396
+ };
397
+
398
+ // src/services/evaluators/true-false-evaluator.ts
399
+ var TrueFalseEvaluator = class {
400
+ async evaluate(question, answer) {
401
+ var _a;
402
+ const points = (_a = question.points) != null ? _a : 0;
403
+ const correctAnswer = question.correctAnswer;
404
+ let userAnswer = answer;
405
+ if (typeof answer === "string") {
406
+ userAnswer = answer.toLowerCase() === "true";
407
+ }
408
+ const isCorrect = typeof userAnswer === "boolean" && userAnswer === correctAnswer;
409
+ const correctAnswerDetail = {
410
+ id: null,
411
+ value: correctAnswer
412
+ };
413
+ return Promise.resolve({
414
+ isCorrect,
415
+ correctAnswer: correctAnswerDetail,
416
+ pointsEarned: isCorrect ? points : 0
417
+ });
418
+ }
419
+ };
420
+
421
+ // src/services/evaluators/short-answer-evaluator.ts
422
+ var ShortAnswerEvaluator = class {
423
+ async evaluate(question, answer) {
424
+ var _a, _b;
425
+ const points = (_a = question.points) != null ? _a : 0;
426
+ let isCorrect = false;
427
+ if (typeof answer === "string") {
428
+ const userAnswerTrimmed = answer.trim();
429
+ const caseSensitive = (_b = question.isCaseSensitive) != null ? _b : false;
430
+ isCorrect = question.acceptedAnswers.some(
431
+ (accAns) => caseSensitive ? accAns.trim() === userAnswerTrimmed : accAns.trim().toLowerCase() === userAnswerTrimmed.toLowerCase()
432
+ );
433
+ }
434
+ const correctAnswerDetail = {
435
+ id: null,
436
+ value: question.acceptedAnswers
437
+ };
438
+ return Promise.resolve({
439
+ isCorrect,
440
+ correctAnswer: correctAnswerDetail,
441
+ pointsEarned: isCorrect ? points : 0
442
+ });
443
+ }
444
+ };
445
+
446
+ // src/services/evaluators/numeric-evaluator.ts
447
+ var NumericEvaluator = class {
448
+ async evaluate(question, answer) {
449
+ var _a;
450
+ const points = (_a = question.points) != null ? _a : 0;
451
+ let isCorrect = false;
452
+ if (typeof answer === "string" || typeof answer === "number") {
453
+ const userAnswerNum = parseFloat(String(answer));
454
+ if (!isNaN(userAnswerNum)) {
455
+ isCorrect = question.tolerance != null ? Math.abs(userAnswerNum - question.answer) <= question.tolerance : userAnswerNum === question.answer;
456
+ }
457
+ }
458
+ const correctAnswerDetail = {
459
+ id: null,
460
+ value: question.answer
461
+ };
462
+ return Promise.resolve({
463
+ isCorrect,
464
+ correctAnswer: correctAnswerDetail,
465
+ pointsEarned: isCorrect ? points : 0
466
+ });
467
+ }
468
+ };
469
+
470
+ // src/services/evaluators/sequence-evaluator.ts
471
+ var SequenceEvaluator = class {
472
+ async evaluate(question, answer) {
473
+ var _a;
474
+ const points = (_a = question.points) != null ? _a : 0;
475
+ let isCorrect = false;
476
+ if (Array.isArray(answer) && answer.length === question.correctOrder.length) {
477
+ isCorrect = answer.every((itemId, index) => itemId === question.correctOrder[index]);
478
+ }
479
+ const correctValues = question.correctOrder.map(
480
+ (id) => {
481
+ var _a2;
482
+ return ((_a2 = question.items.find((item) => item.id === id)) == null ? void 0 : _a2.content) || "";
483
+ }
484
+ );
485
+ const correctAnswerDetail = {
486
+ id: question.correctOrder,
487
+ value: correctValues
488
+ };
489
+ return Promise.resolve({
490
+ isCorrect,
491
+ correctAnswer: correctAnswerDetail,
492
+ pointsEarned: isCorrect ? points : 0
493
+ });
494
+ }
495
+ };
496
+
497
+ // src/services/evaluators/matching-evaluator.ts
498
+ var MatchingEvaluator = class {
499
+ async evaluate(question, answer) {
500
+ var _a;
501
+ const points = (_a = question.points) != null ? _a : 0;
502
+ let isCorrect = false;
503
+ if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
504
+ const userAnswerMap = answer;
505
+ isCorrect = question.correctAnswerMap.length === Object.keys(userAnswerMap).length && question.correctAnswerMap.every((map) => userAnswerMap[map.promptId] === map.optionId);
506
+ }
507
+ const correctMap = question.correctAnswerMap.reduce((acc, curr) => {
508
+ var _a2, _b;
509
+ const promptText = ((_a2 = question.prompts.find((p) => p.id === curr.promptId)) == null ? void 0 : _a2.content) || "";
510
+ const optionText = ((_b = question.options.find((o) => o.id === curr.optionId)) == null ? void 0 : _b.content) || "";
511
+ acc[promptText] = optionText;
512
+ return acc;
513
+ }, {});
514
+ const correctAnswerDetail = {
515
+ id: null,
516
+ value: correctMap
517
+ };
518
+ return Promise.resolve({
519
+ isCorrect,
520
+ correctAnswer: correctAnswerDetail,
521
+ pointsEarned: isCorrect ? points : 0
522
+ });
523
+ }
524
+ };
525
+
526
+ // src/services/evaluators/fill-in-the-blanks-evaluator.ts
527
+ var FillInTheBlanksEvaluator = class {
528
+ async evaluate(question, answer) {
529
+ var _a;
530
+ const points = (_a = question.points) != null ? _a : 0;
531
+ let isCorrect = false;
532
+ if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
533
+ const userAnswerMap = answer;
534
+ isCorrect = question.answers.length > 0 && question.answers.every((correctAnsDef) => {
535
+ var _a2, _b;
536
+ const userValForBlank = (_a2 = userAnswerMap[correctAnsDef.blankId]) == null ? void 0 : _a2.trim();
537
+ if (userValForBlank === void 0) return false;
538
+ const caseSensitive = (_b = question.isCaseSensitive) != null ? _b : false;
539
+ return correctAnsDef.acceptedValues.some(
540
+ (accVal) => caseSensitive ? accVal.trim() === userValForBlank : accVal.trim().toLowerCase() === userValForBlank.toLowerCase()
541
+ );
542
+ });
543
+ }
544
+ const correctMap = question.answers.reduce((acc, curr) => {
545
+ acc[curr.blankId] = curr.acceptedValues.join(" | ");
546
+ return acc;
547
+ }, {});
548
+ const correctAnswerDetail = {
549
+ id: null,
550
+ value: correctMap
551
+ };
552
+ return Promise.resolve({
553
+ isCorrect,
554
+ correctAnswer: correctAnswerDetail,
555
+ pointsEarned: isCorrect ? points : 0
556
+ });
557
+ }
558
+ };
559
+
560
+ // src/services/evaluators/drag-and-drop-evaluator.ts
561
+ var DragAndDropEvaluator = class {
562
+ async evaluate(question, answer) {
563
+ var _a;
564
+ const points = (_a = question.points) != null ? _a : 0;
565
+ let isCorrect = false;
566
+ if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
567
+ const userAnswerMap = answer;
568
+ isCorrect = question.answerMap.length === Object.keys(userAnswerMap).length && question.answerMap.every((map) => userAnswerMap[map.draggableId] === map.dropZoneId);
569
+ }
570
+ const correctMap = question.answerMap.reduce((acc, curr) => {
571
+ var _a2, _b;
572
+ const draggableText = ((_a2 = question.draggableItems.find((d) => d.id === curr.draggableId)) == null ? void 0 : _a2.content) || "";
573
+ const dropZoneText = ((_b = question.dropZones.find((z3) => z3.id === curr.dropZoneId)) == null ? void 0 : _b.label) || "";
574
+ acc[draggableText] = dropZoneText;
575
+ return acc;
576
+ }, {});
577
+ const correctAnswerDetail = {
578
+ id: null,
579
+ value: correctMap
580
+ };
581
+ return Promise.resolve({
582
+ isCorrect,
583
+ correctAnswer: correctAnswerDetail,
584
+ pointsEarned: isCorrect ? points : 0
585
+ });
586
+ }
587
+ };
588
+
589
+ // src/services/evaluators/hotspot-evaluator.ts
590
+ var HotspotEvaluator = class {
591
+ async evaluate(question, answer) {
592
+ var _a;
593
+ const points = (_a = question.points) != null ? _a : 0;
594
+ let isCorrect = false;
595
+ if (Array.isArray(answer)) {
596
+ const userAnswerSet = new Set(answer);
597
+ const correctAnswerSet = new Set(question.correctHotspotIds);
598
+ isCorrect = userAnswerSet.size === correctAnswerSet.size && [...userAnswerSet].every((id) => correctAnswerSet.has(id));
599
+ }
600
+ const correctValues = question.correctHotspotIds.map(
601
+ (id) => {
602
+ var _a2;
603
+ return ((_a2 = question.hotspots.find((h) => h.id === id)) == null ? void 0 : _a2.description) || id;
604
+ }
605
+ );
606
+ const correctAnswerDetail = {
607
+ id: question.correctHotspotIds,
608
+ value: correctValues
609
+ };
610
+ return Promise.resolve({
611
+ isCorrect,
612
+ correctAnswer: correctAnswerDetail,
613
+ pointsEarned: isCorrect ? points : 0
614
+ });
615
+ }
616
+ };
617
+
618
+ // src/services/evaluators/programming-evaluator.ts
619
+ var ProgrammingEvaluator = class {
620
+ async evaluate(question, answer) {
621
+ var _a, _b;
622
+ const points = (_a = question.points) != null ? _a : 0;
623
+ let isCorrect = false;
624
+ if (typeof answer === "string" && typeof question.solutionGeneratedCode === "string") {
625
+ if (typeof window !== "undefined" && ((_b = window.Blockly) == null ? void 0 : _b.JavaScript)) {
626
+ const LocalBlockly = window.Blockly;
627
+ let generatedUserCode = "";
628
+ try {
629
+ const tempWorkspace = new LocalBlockly.Workspace();
630
+ const dom = LocalBlockly.Xml.textToDom(answer);
631
+ LocalBlockly.Xml.domToWorkspace(dom, tempWorkspace);
632
+ generatedUserCode = LocalBlockly.JavaScript.workspaceToCode(tempWorkspace) || "";
633
+ const normalize = (code) => code.replace(/\s+/g, " ").trim();
634
+ isCorrect = normalize(generatedUserCode) === normalize(question.solutionGeneratedCode);
635
+ tempWorkspace.dispose();
636
+ } catch (e) {
637
+ console.error(`Error generating code from user's ${question.questionType} XML for evaluation:`, e);
638
+ isCorrect = false;
639
+ }
640
+ } else {
641
+ console.warn(`Blockly library not available for ${question.questionType} evaluation. Skipping code comparison.`);
642
+ isCorrect = false;
643
+ }
644
+ }
645
+ const correctAnswerDetail = {
646
+ id: null,
647
+ value: question.solutionGeneratedCode || ""
648
+ };
649
+ return Promise.resolve({
650
+ isCorrect,
651
+ correctAnswer: correctAnswerDetail,
652
+ pointsEarned: isCorrect ? points : 0
653
+ });
654
+ }
655
+ };
656
+
657
+ // src/utils/jsonUtils.ts
658
+ var JsonRepairEngine = class {
659
+ /**
660
+ * Attempts to repair unterminated strings in JSON.
661
+ * NOTE: This is a heuristic approach and may not be perfect for all cases.
662
+ */
663
+ static repairUnterminatedStrings(jsonStr) {
664
+ let repaired = jsonStr;
665
+ let inString = false;
666
+ let escaped = false;
667
+ let lastQuoteIndex = -1;
668
+ for (let i = 0; i < repaired.length; i++) {
669
+ const char = repaired[i];
670
+ if (escaped) {
671
+ escaped = false;
672
+ continue;
673
+ }
674
+ if (char === "\\") {
675
+ escaped = true;
676
+ continue;
677
+ }
678
+ if (char === '"') {
679
+ inString = !inString;
680
+ if (inString) {
681
+ lastQuoteIndex = i;
682
+ }
683
+ }
684
+ }
685
+ if (inString && lastQuoteIndex !== -1) {
686
+ const beforeUnterminated = repaired.substring(0, lastQuoteIndex + 1);
687
+ const afterUnterminated = repaired.substring(lastQuoteIndex + 1);
688
+ const breakPoints = [",", "}", "]", "\n"];
689
+ let breakIndex = -1;
690
+ for (let i = 0; i < afterUnterminated.length; i++) {
691
+ if (breakPoints.includes(afterUnterminated[i])) {
692
+ breakIndex = i;
693
+ break;
694
+ }
695
+ }
696
+ if (breakIndex !== -1) {
697
+ const stringContent = afterUnterminated.substring(0, breakIndex);
698
+ const remainder = afterUnterminated.substring(breakIndex);
699
+ const escapedContent = stringContent.replace(new RegExp('(?<!\\\\)"', "g"), '\\"');
700
+ repaired = beforeUnterminated + escapedContent + '"' + remainder;
701
+ } else {
702
+ const escapedContent = afterUnterminated.replace(new RegExp('(?<!\\\\)"', "g"), '\\"');
703
+ repaired = beforeUnterminated + escapedContent + '"';
704
+ }
705
+ }
706
+ return repaired;
707
+ }
708
+ // FIX: Replaced unsafe single quote replacement with a stateful parser.
709
+ /**
710
+ * Safely replaces single quotes with double quotes only for keys and string values,
711
+ * ignoring apostrophes inside already double-quoted strings.
712
+ */
713
+ static safelyFixQuotes(jsonStr) {
714
+ let result = "";
715
+ let inDoubleQuoteString = false;
716
+ let escaped = false;
717
+ for (let i = 0; i < jsonStr.length; i++) {
718
+ const char = jsonStr[i];
719
+ if (escaped) {
720
+ result += char;
721
+ escaped = false;
722
+ continue;
723
+ }
724
+ if (char === "\\") {
725
+ escaped = true;
726
+ result += char;
727
+ continue;
728
+ }
729
+ if (char === '"') {
730
+ inDoubleQuoteString = !inDoubleQuoteString;
731
+ }
732
+ if (char === "'" && !inDoubleQuoteString) {
733
+ result += '"';
734
+ } else {
735
+ result += char;
736
+ }
737
+ }
738
+ return result;
739
+ }
740
+ /**
741
+ * Fixes common JSON formatting issues using more robust methods.
742
+ */
743
+ static applyCommonFixes(jsonStr) {
744
+ let fixed = jsonStr;
745
+ fixed = this.safelyFixQuotes(fixed);
746
+ fixed = fixed.replace(/,\s*([}\]])/g, "$1");
747
+ fixed = fixed.replace(/("|}|\d|]|true|false|null)\s*\n\s*(")/g, "$1,\n$2");
748
+ fixed = fixed.replace(/"[\s\S]*?"/g, (match) => {
749
+ const content = match.substring(1, match.length - 1);
750
+ const fixedContent = content.replace(/\n/g, "\\n").replace(/\r/g, "\\r");
751
+ return `"${fixedContent}"`;
752
+ });
753
+ fixed = fixed.replace(/"(true|false|null)"/g, "$1");
754
+ return fixed;
755
+ }
756
+ /**
757
+ * Validates JSON by attempting to parse and providing detailed error info.
758
+ */
759
+ static validateAndGetError(jsonStr) {
760
+ try {
761
+ JSON.parse(jsonStr);
762
+ return { isValid: true };
763
+ } catch (error) {
764
+ const errorMessage = error.message || "";
765
+ const positionMatch = errorMessage.match(/position (\d+)/);
766
+ const position = positionMatch ? parseInt(positionMatch[1], 10) : void 0;
767
+ return {
768
+ isValid: false,
769
+ error: errorMessage,
770
+ position
771
+ };
772
+ }
773
+ }
774
+ /**
775
+ * Main repair function that attempts multiple strategies.
776
+ */
777
+ static repairJson(jsonStr) {
778
+ var _a;
779
+ let current = jsonStr.trim();
780
+ const maxAttempts = 5;
781
+ let lastError = "";
782
+ let lastPosition = -1;
783
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
784
+ const validation = this.validateAndGetError(current);
785
+ if (validation.isValid) {
786
+ return current;
787
+ }
788
+ console.warn(`JSON repair attempt ${attempt + 1}: ${validation.error}`);
789
+ if (validation.error === lastError && validation.position === lastPosition) {
790
+ console.error("Repair attempt stuck on the same error, aborting this strategy.");
791
+ if (validation.position) {
792
+ const truncated = current.substring(0, validation.position);
793
+ const openBraces = (truncated.match(/{/g) || []).length;
794
+ const closeBraces = (truncated.match(/}/g) || []).length;
795
+ const openBrackets = (truncated.match(/\[/g) || []).length;
796
+ const closeBrackets = (truncated.match(/\]/g) || []).length;
797
+ let repaired = truncated.replace(/,\s*$/, "");
798
+ for (let i = 0; i < openBrackets - closeBrackets; i++) repaired += "]";
799
+ for (let i = 0; i < openBraces - closeBraces; i++) repaired += "}";
800
+ current = repaired;
801
+ const finalValidation = this.validateAndGetError(current);
802
+ if (finalValidation.isValid) return current;
803
+ }
804
+ break;
805
+ }
806
+ lastError = validation.error || "";
807
+ lastPosition = validation.position;
808
+ if ((_a = validation.error) == null ? void 0 : _a.includes("Unterminated string")) {
809
+ current = this.repairUnterminatedStrings(current);
810
+ } else {
811
+ current = this.applyCommonFixes(current);
812
+ }
813
+ }
814
+ try {
815
+ let finalAttempt = this.applyCommonFixes(jsonStr.trim());
816
+ finalAttempt = this.repairUnterminatedStrings(finalAttempt);
817
+ JSON.parse(finalAttempt);
818
+ return finalAttempt;
819
+ } catch (e) {
820
+ throw new Error(`Unable to repair JSON after ${maxAttempts} attempts. Last known error: ${lastError}`);
821
+ }
822
+ }
823
+ };
824
+ function extractJsonFromMarkdown(text) {
825
+ if (!text) {
826
+ throw new Error("Input text is empty or null.");
827
+ }
828
+ const trimmedText = text.trim();
829
+ try {
830
+ JSON.parse(trimmedText);
831
+ return trimmedText;
832
+ } catch (e) {
833
+ }
834
+ const markdownPatterns = [
835
+ /```(?:json|JSON)\s*([\s\S]*?)\s*```/,
836
+ // ```json ... ```
837
+ /```\s*({[\s\S]*?}|\[[\s\S]*?\])\s*```/
838
+ // ``` { ... } ``` or ``` [ ... ] ```
839
+ ];
840
+ for (const pattern of markdownPatterns) {
841
+ const match = trimmedText.match(pattern);
842
+ if (match && match[1]) {
843
+ const content = match[1].trim();
844
+ try {
845
+ JSON.parse(content);
846
+ return content;
847
+ } catch (e) {
848
+ console.warn("JSON inside markdown block is invalid, attempting repair...");
849
+ try {
850
+ return JsonRepairEngine.repairJson(content);
851
+ } catch (repairError) {
852
+ console.warn(`Markdown block repair failed: ${repairError.message}. Trying other strategies...`);
853
+ }
854
+ }
855
+ }
856
+ }
857
+ const firstBrace = trimmedText.indexOf("{");
858
+ const firstBracket = trimmedText.indexOf("[");
859
+ let startIndex = -1;
860
+ if (firstBrace === -1 && firstBracket === -1) ; else if (firstBrace === -1) {
861
+ startIndex = firstBracket;
862
+ } else if (firstBracket === -1) {
863
+ startIndex = firstBrace;
864
+ } else {
865
+ startIndex = Math.min(firstBrace, firstBracket);
866
+ }
867
+ if (startIndex !== -1) {
868
+ const textToProcess = trimmedText.substring(startIndex);
869
+ let balance = 0;
870
+ let inString = false;
871
+ let escaped = false;
872
+ const startChar = textToProcess[0];
873
+ const endChar = startChar === "{" ? "}" : "]";
874
+ for (let i = 0; i < textToProcess.length; i++) {
875
+ const char = textToProcess[i];
876
+ if (escaped) {
877
+ escaped = false;
878
+ continue;
879
+ }
880
+ if (char === "\\") {
881
+ escaped = true;
882
+ continue;
883
+ }
884
+ if (char === '"') {
885
+ inString = !inString;
886
+ }
887
+ if (!inString) {
888
+ if (char === startChar) balance++;
889
+ if (char === endChar) balance--;
890
+ }
891
+ if (balance === 0 && i > 0) {
892
+ const potentialJson = textToProcess.substring(0, i + 1);
893
+ try {
894
+ JSON.parse(potentialJson);
895
+ return potentialJson;
896
+ } catch (e) {
897
+ console.warn(`Balanced JSON segment is invalid, attempting repair...`);
898
+ try {
899
+ return JsonRepairEngine.repairJson(potentialJson);
900
+ } catch (repairError) {
901
+ console.warn(`Repair failed for balanced segment: ${repairError.message}`);
902
+ }
903
+ }
904
+ break;
905
+ }
906
+ }
907
+ }
908
+ console.warn("All extraction strategies failed, attempting to repair the entire input text as a last resort.");
909
+ try {
910
+ return JsonRepairEngine.repairJson(trimmedText);
911
+ } catch (finalError) {
912
+ throw new Error(`Unable to extract or repair valid JSON from AI response. Preview: "${trimmedText.substring(0, 100)}...". Final error: ${finalError.message}`);
913
+ }
914
+ }
915
+ zod.z.object({
916
+ language: zod.z.custom(),
917
+ problemPrompt: zod.z.string(),
918
+ userCode: zod.z.string(),
919
+ testCase: zod.z.custom()
920
+ });
921
+ var AIEvaluationOutputSchema = zod.z.object({
922
+ passed: zod.z.boolean().describe("Did the user's code produce the expected output for the given input?"),
923
+ actualOutput: zod.z.any().describe("The actual output produced by the user's code."),
924
+ reasoning: zod.z.string().describe("A brief explanation of why the code passed or failed, or if there was a syntax error.")
925
+ });
926
+ var EvaluateUserCodeOutputSchema = AIEvaluationOutputSchema;
927
+
928
+ // src/ai/flows/evaluate-user-code.ts
929
+ async function evaluateUserCode(clientInput, apiKey) {
930
+ try {
931
+ const ai = genkit.genkit({
932
+ plugins: [googleai.googleAI({ apiKey })],
933
+ model: googleai.gemini20Flash
934
+ });
935
+ const { language, problemPrompt, userCode, testCase } = clientInput;
936
+ const promptText = `
937
+ You are an expert Code Judge and Teaching Assistant for a ${language} programming course.
938
+ Your task is to evaluate a student's code submission for a specific problem against a single test case.
939
+
940
+ ## Problem Description
941
+ ${problemPrompt}
942
+
943
+ ## Student's Code Submission
944
+ \`\`\`${language}
945
+ ${userCode}
946
+ \`\`\`
947
+
948
+ ## Test Case to Evaluate
949
+ - Input(s): ${JSON.stringify(testCase.input)}
950
+ - Expected Output: ${JSON.stringify(testCase.expectedOutput)}
951
+
952
+ ## Your Task
953
+ 1. **Analyze Execution:** Mentally execute the student's code with the provided input(s).
954
+ 2. **Determine Output:** Figure out what the actual output of the code would be.
955
+ 3. **Compare:** Compare the actual output with the expected output.
956
+ 4. **Handle Errors:** If the code has a syntax error or would crash, treat it as a failure.
957
+ 5. **Provide Reasoning:** Briefly explain your conclusion. If it failed, explain why (e.g., "incorrect result", "infinite loop", "syntax error on line 5").
958
+
959
+ **CRITICAL JSON OUTPUT FORMAT:**
960
+ Return ONLY the JSON object with this EXACT structure.
961
+
962
+ \`\`\`json
963
+ {
964
+ "passed": false,
965
+ "actualOutput": 5,
966
+ "reasoning": "The function correctly summed the numbers but did not filter for only even numbers."
967
+ }
968
+ \`\`\`
969
+
970
+ Return only the JSON response.`;
971
+ const response = await ai.generate(promptText);
972
+ const rawText = response.text;
973
+ const jsonText = extractJsonFromMarkdown(rawText);
974
+ const aiGeneratedContent = JSON.parse(jsonText);
975
+ return EvaluateUserCodeOutputSchema.parse(aiGeneratedContent);
976
+ } catch (error) {
977
+ console.error("Error evaluating user code:", error);
978
+ if (error instanceof zod.z.ZodError) {
979
+ throw new Error(`AI evaluation output validation failed: ${error.message}`);
980
+ }
981
+ return {
982
+ passed: false,
983
+ actualOutput: "Evaluation Error",
984
+ reasoning: `The AI judge failed to process the code. Error: ${error.message}`
985
+ };
986
+ }
987
+ }
988
+
989
+ // src/services/APIKeyService.ts
990
+ var GEMINI_API_KEY_SERVICE_NAME = "gemini";
991
+ var LOCAL_STORAGE_PREFIX = "iqk_api_keys_";
992
+ function _encode(data) {
993
+ if (typeof window !== "undefined" && typeof window.btoa === "function") {
994
+ try {
995
+ return window.btoa(data);
996
+ } catch (e) {
997
+ console.error("Base64 encoding (btoa) failed:", e);
998
+ return data;
999
+ }
1000
+ }
1001
+ return data;
1002
+ }
1003
+ function _decode(data) {
1004
+ if (typeof window !== "undefined" && typeof window.atob === "function") {
1005
+ try {
1006
+ return window.atob(data);
1007
+ } catch (e) {
1008
+ console.error("Base64 decoding (atob) failed:", e);
1009
+ return data;
1010
+ }
1011
+ }
1012
+ return data;
1013
+ }
1014
+ var APIKeyService = class {
1015
+ static getStorageKey(serviceName) {
1016
+ return `${LOCAL_STORAGE_PREFIX}${serviceName}`;
1017
+ }
1018
+ /**
1019
+ * Saves an API key to localStorage. The key is mildly obfuscated using Base64.
1020
+ * @param serviceName - The name of the service (e.g., 'gemini').
1021
+ * @param apiKey - The API key to save.
1022
+ */
1023
+ static saveAPIKey(serviceName, apiKey) {
1024
+ if (typeof window !== "undefined" && window.localStorage) {
1025
+ try {
1026
+ const encodedKey = _encode(apiKey);
1027
+ localStorage.setItem(this.getStorageKey(serviceName), encodedKey);
1028
+ } catch (e) {
1029
+ console.error(`Error saving API key for ${serviceName} to localStorage:`, e);
1030
+ }
1031
+ } else {
1032
+ console.warn("localStorage is not available. APIKeyService cannot save keys.");
1033
+ }
1034
+ }
1035
+ /**
1036
+ * Retrieves an API key from localStorage.
1037
+ * @param serviceName - The name of the service.
1038
+ * @returns The decoded API key, or null if not found or if localStorage is unavailable.
1039
+ */
1040
+ static getAPIKey(serviceName) {
1041
+ if (typeof window !== "undefined" && window.localStorage) {
1042
+ try {
1043
+ const storedKey = localStorage.getItem(this.getStorageKey(serviceName));
1044
+ if (storedKey) {
1045
+ return _decode(storedKey);
1046
+ }
1047
+ } catch (e) {
1048
+ console.error(`Error retrieving API key for ${serviceName} from localStorage:`, e);
1049
+ }
1050
+ }
1051
+ return null;
1052
+ }
1053
+ /**
1054
+ * Removes an API key from localStorage.
1055
+ * @param serviceName - The name of the service.
1056
+ */
1057
+ static removeAPIKey(serviceName) {
1058
+ if (typeof window !== "undefined" && window.localStorage) {
1059
+ try {
1060
+ localStorage.removeItem(this.getStorageKey(serviceName));
1061
+ } catch (e) {
1062
+ console.error(`Error removing API key for ${serviceName} from localStorage:`, e);
1063
+ }
1064
+ }
1065
+ }
1066
+ /**
1067
+ * Checks if an API key exists in localStorage for the given service.
1068
+ * @param serviceName - The name of the service.
1069
+ * @returns True if a key exists, false otherwise.
1070
+ */
1071
+ static hasAPIKey(serviceName) {
1072
+ return this.getAPIKey(serviceName) !== null;
1073
+ }
1074
+ };
1075
+
1076
+ // src/services/CodeEvaluationService.ts
1077
+ var CodeEvaluationService = class {
1078
+ constructor() {
1079
+ this.apiKey = APIKeyService.getAPIKey(GEMINI_API_KEY_SERVICE_NAME);
1080
+ }
1081
+ /**
1082
+ * Evaluates a user's code against a single test case using an AI judge.
1083
+ * @param question The full CodingQuestion object.
1084
+ * @param userCode The user's submitted code string.
1085
+ * @param testCase The specific TestCase to evaluate against.
1086
+ * @returns A promise that resolves to an EvaluationResult object.
1087
+ */
1088
+ async evaluateSingleTestCase(question, userCode, testCase) {
1089
+ if (!this.apiKey) {
1090
+ return {
1091
+ testCaseId: testCase.id,
1092
+ passed: false,
1093
+ actualOutput: "Configuration Error",
1094
+ reasoning: "API Key is not configured."
1095
+ };
1096
+ }
1097
+ const aiResult = await evaluateUserCode({
1098
+ language: question.language,
1099
+ problemPrompt: question.prompt,
1100
+ userCode,
1101
+ testCase
1102
+ }, this.apiKey);
1103
+ return __spreadValues({
1104
+ testCaseId: testCase.id
1105
+ }, aiResult);
1106
+ }
1107
+ /**
1108
+ * Evaluates user's code against all test cases for a given question.
1109
+ * @param question The full CodingQuestion object.
1110
+ * @param userCode The user's submitted code string.
1111
+ * @returns A promise that resolves to an array of EvaluationResult objects.
1112
+ */
1113
+ async evaluateAllTestCases(question, userCode) {
1114
+ const results = [];
1115
+ for (const testCase of question.testCases) {
1116
+ const result = await this.evaluateSingleTestCase(question, userCode, testCase);
1117
+ results.push(result);
1118
+ }
1119
+ return results;
1120
+ }
1121
+ /**
1122
+ * Evaluates user's code against only the public test cases for a given question.
1123
+ * Useful for a "Run Tests" button before final submission.
1124
+ * @param question The full CodingQuestion object.
1125
+ * @param userCode The user's submitted code string.
1126
+ * @returns A promise that resolves to an array of EvaluationResult objects.
1127
+ */
1128
+ async evaluatePublicTestCases(question, userCode) {
1129
+ const publicTestCases = question.testCases.filter((tc) => tc.isPublic);
1130
+ const results = [];
1131
+ for (const testCase of publicTestCases) {
1132
+ const result = await this.evaluateSingleTestCase(question, userCode, testCase);
1133
+ results.push(result);
1134
+ }
1135
+ return results;
1136
+ }
1137
+ };
1138
+
1139
+ // src/services/evaluators/coding-evaluator.ts
1140
+ var CodingEvaluator = class {
1141
+ async evaluate(question, answer) {
1142
+ var _a;
1143
+ const points = (_a = question.points) != null ? _a : 0;
1144
+ if (typeof answer !== "string" || !answer.trim()) {
1145
+ return {
1146
+ isCorrect: false,
1147
+ correctAnswer: { id: null, value: question.solutionCode },
1148
+ pointsEarned: 0,
1149
+ evaluationDetails: question.testCases.map((tc) => ({
1150
+ testCaseId: tc.id,
1151
+ passed: false,
1152
+ actualOutput: "No submission",
1153
+ reasoning: "User did not submit any code."
1154
+ }))
1155
+ };
1156
+ }
1157
+ try {
1158
+ const evaluationService = new CodeEvaluationService();
1159
+ const testCaseResults = await evaluationService.evaluateAllTestCases(question, answer);
1160
+ const isCorrect = testCaseResults.every((result) => result.passed);
1161
+ const correctAnswerDetail = {
1162
+ id: null,
1163
+ value: question.solutionCode
1164
+ };
1165
+ return {
1166
+ isCorrect,
1167
+ correctAnswer: correctAnswerDetail,
1168
+ pointsEarned: isCorrect ? points : 0,
1169
+ evaluationDetails: testCaseResults
1170
+ // Pass through the detailed results
1171
+ };
1172
+ } catch (error) {
1173
+ console.error("A critical error occurred during code evaluation:", error);
1174
+ return {
1175
+ isCorrect: false,
1176
+ correctAnswer: { id: null, value: question.solutionCode },
1177
+ pointsEarned: 0,
1178
+ evaluationDetails: question.testCases.map((tc) => ({
1179
+ testCaseId: tc.id,
1180
+ passed: false,
1181
+ actualOutput: "Evaluation Error",
1182
+ reasoning: error instanceof Error ? error.message : "An unknown error occurred."
1183
+ }))
1184
+ };
1185
+ }
1186
+ }
1187
+ };
1188
+
337
1189
  // src/services/QuizEngine.ts
338
1190
  var QuizEngine = class {
339
1191
  constructor(options) {
@@ -350,6 +1202,8 @@ var QuizEngine = class {
350
1202
  this.callbacks = options.callbacks || {};
351
1203
  this.questions = ((_a = this.config.settings) == null ? void 0 : _a.shuffleQuestions) ? [...this.config.questions].sort(() => Math.random() - 0.5) : this.config.questions;
352
1204
  this.overallStartTime = Date.now();
1205
+ this.evaluators = /* @__PURE__ */ new Map();
1206
+ this.registerEvaluators();
353
1207
  if (((_b = this.config.settings) == null ? void 0 : _b.timeLimitMinutes) && this.config.settings.timeLimitMinutes > 0) {
354
1208
  this.timeLeftInSeconds = this.config.settings.timeLimitMinutes * 60;
355
1209
  }
@@ -388,6 +1242,22 @@ var QuizEngine = class {
388
1242
  }
389
1243
  (_e = (_d = this.callbacks).onQuestionChange) == null ? void 0 : _e.call(_d, initialQ, this.getCurrentQuestionNumber(), this.getTotalQuestions());
390
1244
  }
1245
+ registerEvaluators() {
1246
+ this.evaluators.set("multiple_choice", new MultipleChoiceEvaluator());
1247
+ this.evaluators.set("multiple_response", new MultipleResponseEvaluator());
1248
+ this.evaluators.set("true_false", new TrueFalseEvaluator());
1249
+ this.evaluators.set("short_answer", new ShortAnswerEvaluator());
1250
+ this.evaluators.set("numeric", new NumericEvaluator());
1251
+ this.evaluators.set("sequence", new SequenceEvaluator());
1252
+ this.evaluators.set("matching", new MatchingEvaluator());
1253
+ this.evaluators.set("fill_in_the_blanks", new FillInTheBlanksEvaluator());
1254
+ this.evaluators.set("drag_and_drop", new DragAndDropEvaluator());
1255
+ this.evaluators.set("hotspot", new HotspotEvaluator());
1256
+ const programmingEvaluator = new ProgrammingEvaluator();
1257
+ this.evaluators.set("blockly_programming", programmingEvaluator);
1258
+ this.evaluators.set("scratch_programming", programmingEvaluator);
1259
+ this.evaluators.set("coding", new CodingEvaluator());
1260
+ }
391
1261
  _recordCurrentQuestionTime() {
392
1262
  if (this.questionStartTime && this.currentQuestionIndex >= 0 && this.currentQuestionIndex < this.questions.length) {
393
1263
  const currentQId = this.questions[this.currentQuestionIndex].id;
@@ -480,180 +1350,28 @@ var QuizEngine = class {
480
1350
  }
481
1351
  return this.getCurrentQuestion();
482
1352
  }
483
- evaluateQuestion(question, answer) {
484
- var _a, _b, _c;
485
- let isCorrect = false;
486
- let correctAnswerDetail = null;
487
- const points = (_a = question.points) != null ? _a : 0;
488
- const findOptionText = (q, id) => {
489
- var _a2;
490
- return ((_a2 = q.options.find((opt) => opt.id === id)) == null ? void 0 : _a2.text) || "";
491
- };
492
- switch (question.questionType) {
493
- case "multiple_choice": {
494
- const q = question;
495
- const correctAnswerId = q.correctAnswerId;
496
- const correctValue = findOptionText(q, correctAnswerId);
497
- correctAnswerDetail = { id: correctAnswerId, value: correctValue };
498
- isCorrect = answer === correctAnswerId;
499
- break;
500
- }
501
- case "multiple_response": {
502
- const q = question;
503
- const correctAnswerIds = q.correctAnswerIds;
504
- const correctValues = correctAnswerIds.map((id) => findOptionText(q, id));
505
- correctAnswerDetail = { id: correctAnswerIds, value: correctValues };
506
- if (Array.isArray(answer)) {
507
- const userAnswerSet = new Set(answer);
508
- const correctAnswerSet = new Set(correctAnswerIds);
509
- isCorrect = userAnswerSet.size === correctAnswerSet.size && [...userAnswerSet].every((id) => correctAnswerSet.has(id));
510
- }
511
- break;
512
- }
513
- case "true_false": {
514
- const q = question;
515
- correctAnswerDetail = { id: null, value: q.correctAnswer };
516
- let tfAnswer = answer;
517
- if (typeof answer === "string") tfAnswer = answer.toLowerCase() === "true";
518
- isCorrect = typeof tfAnswer === "boolean" && tfAnswer === q.correctAnswer;
519
- break;
520
- }
521
- case "short_answer": {
522
- const q = question;
523
- correctAnswerDetail = { id: null, value: q.acceptedAnswers };
524
- if (typeof answer === "string") {
525
- const userAnswerTrimmed = answer.trim();
526
- const caseSensitive = (_b = q.isCaseSensitive) != null ? _b : false;
527
- isCorrect = q.acceptedAnswers.some((accAns) => caseSensitive ? accAns.trim() === userAnswerTrimmed : accAns.trim().toLowerCase() === userAnswerTrimmed.toLowerCase());
528
- }
529
- break;
530
- }
531
- case "numeric": {
532
- const q = question;
533
- correctAnswerDetail = { id: null, value: q.answer };
534
- if (typeof answer === "string" || typeof answer === "number") {
535
- const userAnswerNum = parseFloat(String(answer));
536
- if (!isNaN(userAnswerNum)) {
537
- isCorrect = q.tolerance != null ? Math.abs(userAnswerNum - q.answer) <= q.tolerance : userAnswerNum === q.answer;
538
- }
539
- }
540
- break;
541
- }
542
- case "sequence": {
543
- const q = question;
544
- const correctValues = q.correctOrder.map((id) => {
545
- var _a2;
546
- return ((_a2 = q.items.find((item) => item.id === id)) == null ? void 0 : _a2.content) || "";
547
- });
548
- correctAnswerDetail = { id: q.correctOrder, value: correctValues };
549
- if (Array.isArray(answer) && answer.length === q.correctOrder.length) {
550
- isCorrect = answer.every((itemId, index) => itemId === q.correctOrder[index]);
551
- }
552
- break;
553
- }
554
- case "matching": {
555
- const q = question;
556
- const correctMap = q.correctAnswerMap.reduce((acc, curr) => {
557
- var _a2, _b2;
558
- const promptText = ((_a2 = q.prompts.find((p) => p.id === curr.promptId)) == null ? void 0 : _a2.content) || "";
559
- const optionText = ((_b2 = q.options.find((o) => o.id === curr.optionId)) == null ? void 0 : _b2.content) || "";
560
- acc[promptText] = optionText;
561
- return acc;
562
- }, {});
563
- correctAnswerDetail = { id: null, value: correctMap };
564
- if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
565
- const userAnswerMap = answer;
566
- isCorrect = q.correctAnswerMap.length === Object.keys(userAnswerMap).length && q.correctAnswerMap.every((map) => userAnswerMap[map.promptId] === map.optionId);
567
- }
568
- break;
569
- }
570
- case "fill_in_the_blanks": {
571
- const q = question;
572
- const correctMap = q.answers.reduce((acc, curr) => {
573
- acc[curr.blankId] = curr.acceptedValues.join(" | ");
574
- return acc;
575
- }, {});
576
- correctAnswerDetail = { id: null, value: correctMap };
577
- if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
578
- const userAnswerMap = answer;
579
- isCorrect = q.answers.every((correctAnsDef) => {
580
- var _a2, _b2;
581
- const userValForBlank = (_a2 = userAnswerMap[correctAnsDef.blankId]) == null ? void 0 : _a2.trim();
582
- if (userValForBlank === void 0) return false;
583
- const caseSensitive = (_b2 = q.isCaseSensitive) != null ? _b2 : false;
584
- return correctAnsDef.acceptedValues.some((accVal) => caseSensitive ? accVal.trim() === userValForBlank : accVal.trim().toLowerCase() === userValForBlank.toLowerCase());
585
- });
586
- }
587
- break;
588
- }
589
- case "drag_and_drop": {
590
- const q = question;
591
- const correctMap = q.answerMap.reduce((acc, curr) => {
592
- var _a2, _b2;
593
- const draggableText = ((_a2 = q.draggableItems.find((d) => d.id === curr.draggableId)) == null ? void 0 : _a2.content) || "";
594
- const dropZoneText = ((_b2 = q.dropZones.find((z) => z.id === curr.dropZoneId)) == null ? void 0 : _b2.label) || "";
595
- acc[draggableText] = dropZoneText;
596
- return acc;
597
- }, {});
598
- correctAnswerDetail = { id: null, value: correctMap };
599
- if (typeof answer === "object" && answer !== null && !Array.isArray(answer)) {
600
- const userAnswerMap = answer;
601
- isCorrect = q.answerMap.length === Object.keys(userAnswerMap).length && q.answerMap.every((map) => userAnswerMap[map.draggableId] === map.dropZoneId);
602
- }
603
- break;
604
- }
605
- case "hotspot": {
606
- const q = question;
607
- const correctValues = q.correctHotspotIds.map((id) => {
608
- var _a2;
609
- return ((_a2 = q.hotspots.find((h) => h.id === id)) == null ? void 0 : _a2.description) || id;
610
- });
611
- correctAnswerDetail = { id: q.correctHotspotIds, value: correctValues };
612
- if (Array.isArray(answer)) {
613
- const userAnswerSet = new Set(answer);
614
- const correctAnswerSet = new Set(q.correctHotspotIds);
615
- isCorrect = userAnswerSet.size === correctAnswerSet.size && [...userAnswerSet].every((id) => correctAnswerSet.has(id));
616
- }
617
- break;
618
- }
619
- case "blockly_programming":
620
- case "scratch_programming": {
621
- const q = question;
622
- correctAnswerDetail = { id: null, value: q.solutionGeneratedCode || "" };
623
- if (typeof answer === "string" && typeof q.solutionGeneratedCode === "string") {
624
- if (typeof window !== "undefined" && ((_c = window.Blockly) == null ? void 0 : _c.JavaScript)) {
625
- const LocalBlockly = window.Blockly;
626
- let generatedUserCode = "";
627
- try {
628
- const tempWorkspace = new LocalBlockly.Workspace();
629
- const dom = LocalBlockly.Xml.textToDom(answer);
630
- LocalBlockly.Xml.domToWorkspace(dom, tempWorkspace);
631
- generatedUserCode = LocalBlockly.JavaScript.workspaceToCode(tempWorkspace) || "";
632
- const normalize = (code) => code.replace(/\s+/g, " ").trim();
633
- isCorrect = normalize(generatedUserCode) === normalize(q.solutionGeneratedCode);
634
- tempWorkspace.dispose();
635
- } catch (e) {
636
- console.error(`Error generating code from user's ${q.questionType} XML for evaluation:`, e);
637
- isCorrect = false;
638
- }
639
- } else {
640
- console.warn(`Blockly library not available in QuizEngine for ${q.questionType} code generation during evaluation. Skipping code comparison.`);
641
- isCorrect = false;
642
- }
1353
+ getElapsedTime() {
1354
+ return Date.now() - this.overallStartTime;
1355
+ }
1356
+ destroy() {
1357
+ this.stopTimer();
1358
+ this._recordCurrentQuestionTime();
1359
+ if (this.scormService && this.scormService.hasAPI()) {
1360
+ if (["initialized", "committed", "sending_data"].includes(this.quizResultState.scormStatus || "")) {
1361
+ const termResult = this.scormService.terminate();
1362
+ if (termResult.success) {
1363
+ this.quizResultState.scormStatus = "terminated";
1364
+ } else {
1365
+ this.quizResultState.scormStatus = "error";
1366
+ this.quizResultState.scormError = termResult.error || "SCORM termination failed on destroy.";
643
1367
  }
644
- break;
645
- }
646
- default: {
647
- const _exhaustiveCheck = question;
648
- console.warn("Unsupported question type in QuizEngine evaluation:", _exhaustiveCheck);
649
- isCorrect = false;
650
- correctAnswerDetail = { id: null, value: "Evaluation not implemented." };
651
1368
  }
652
1369
  }
653
- return { isCorrect, correctAnswer: correctAnswerDetail, pointsEarned: isCorrect ? points : 0 };
1370
+ this.scormService = null;
654
1371
  }
1372
+ // (Tiếp theo từ Phần 1)
655
1373
  async calculateResults() {
656
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
1374
+ var _a, _b, _c, _d, _e;
657
1375
  this.stopTimer();
658
1376
  this._recordCurrentQuestionTime();
659
1377
  let totalScore = 0;
@@ -663,92 +1381,31 @@ var QuizEngine = class {
663
1381
  for (const question of this.questions) {
664
1382
  const userAnswerRaw = this.userAnswers.get(question.id) || null;
665
1383
  maxScore += (_a = question.points) != null ? _a : 0;
666
- const { isCorrect, correctAnswer: correctAnswerDetail, pointsEarned } = this.evaluateQuestion(question, userAnswerRaw);
667
- totalScore += pointsEarned;
668
- const timeSpentOnThisQuestion = parseFloat((this.questionTimings.get(question.id) || 0).toFixed(2));
669
- accumulatedTotalTimeSpent += timeSpentOnThisQuestion;
670
- let userAnswerDetail = null;
671
- let allOptions = void 0;
672
- if (userAnswerRaw !== null) {
673
- switch (question.questionType) {
674
- case "multiple_choice": {
675
- const q = question;
676
- allOptions = q.options.map((opt) => ({ id: opt.id, value: opt.text }));
677
- const id = userAnswerRaw;
678
- userAnswerDetail = { id, value: ((_b = allOptions.find((opt) => opt.id === id)) == null ? void 0 : _b.value) || "" };
679
- break;
680
- }
681
- case "multiple_response": {
682
- const q = question;
683
- allOptions = q.options.map((opt) => ({ id: opt.id, value: opt.text }));
684
- const ids = userAnswerRaw;
685
- const values = ids.map((id) => {
686
- var _a2;
687
- return ((_a2 = allOptions == null ? void 0 : allOptions.find((opt) => opt.id === id)) == null ? void 0 : _a2.value) || "";
688
- });
689
- userAnswerDetail = { id: ids, value: values };
690
- break;
691
- }
692
- case "true_false":
693
- case "short_answer":
694
- case "numeric":
695
- userAnswerDetail = { id: null, value: userAnswerRaw };
696
- break;
697
- case "sequence": {
698
- const q = question;
699
- allOptions = q.items.map((item) => ({ id: item.id, value: item.content }));
700
- const ids = userAnswerRaw;
701
- const values = ids.map((id) => {
702
- var _a2;
703
- return ((_a2 = allOptions == null ? void 0 : allOptions.find((opt) => opt.id === id)) == null ? void 0 : _a2.value) || "";
704
- });
705
- userAnswerDetail = { id: ids, value: values };
706
- break;
707
- }
708
- case "matching": {
709
- const q = question;
710
- const userAnswerMap = userAnswerRaw;
711
- const valueMap = {};
712
- for (const promptId in userAnswerMap) {
713
- const optionId = userAnswerMap[promptId];
714
- const promptText = ((_c = q.prompts.find((p) => p.id === promptId)) == null ? void 0 : _c.content) || "";
715
- const optionText = ((_d = q.options.find((o) => o.id === optionId)) == null ? void 0 : _d.content) || "";
716
- valueMap[promptText] = optionText;
717
- }
718
- userAnswerDetail = { id: null, value: valueMap };
719
- break;
720
- }
721
- // --- LOGIC MỚI ĐƯỢC THÊM VÀO ---
722
- case "fill_in_the_blanks": {
723
- if (typeof userAnswerRaw === "object" && userAnswerRaw !== null && !Array.isArray(userAnswerRaw)) {
724
- userAnswerDetail = { id: null, value: userAnswerRaw };
725
- }
726
- break;
727
- }
728
- case "drag_and_drop": {
729
- const q = question;
730
- if (typeof userAnswerRaw === "object" && userAnswerRaw !== null && !Array.isArray(userAnswerRaw)) {
731
- const userAnswerMapByIds = userAnswerRaw;
732
- const enrichedUserAnswerMap = {};
733
- for (const draggableId in userAnswerMapByIds) {
734
- const dropZoneId = userAnswerMapByIds[draggableId];
735
- const draggableText = ((_e = q.draggableItems.find((d) => d.id === draggableId)) == null ? void 0 : _e.content) || `(ID: ${draggableId})`;
736
- const dropZoneText = ((_f = q.dropZones.find((z) => z.id === dropZoneId)) == null ? void 0 : _f.label) || `(ID: ${dropZoneId})`;
737
- enrichedUserAnswerMap[draggableText] = dropZoneText;
738
- }
739
- userAnswerDetail = { id: null, value: enrichedUserAnswerMap };
740
- }
741
- break;
742
- }
743
- // ------------------------------------
744
- // Các loại câu hỏi còn lại vẫn giữ fallback
745
- case "hotspot":
746
- case "blockly_programming":
747
- case "scratch_programming":
748
- userAnswerDetail = { id: null, value: userAnswerRaw };
749
- break;
750
- }
1384
+ const evaluator = this.evaluators.get(question.questionType);
1385
+ if (!evaluator) {
1386
+ console.warn(`No evaluator found for question type: ${question.questionType}`);
1387
+ questionResultsArray.push({
1388
+ questionId: question.id,
1389
+ questionType: question.questionType,
1390
+ prompt: question.prompt,
1391
+ isCorrect: false,
1392
+ pointsEarned: 0,
1393
+ userAnswer: { id: null, value: userAnswerRaw },
1394
+ correctAnswer: { id: null, value: "Evaluation not implemented." },
1395
+ timeSpentSeconds: parseFloat((this.questionTimings.get(question.id) || 0).toFixed(2))
1396
+ });
1397
+ continue;
751
1398
  }
1399
+ const {
1400
+ isCorrect,
1401
+ correctAnswer: correctAnswerDetail,
1402
+ pointsEarned,
1403
+ evaluationDetails
1404
+ } = await evaluator.evaluate(question, userAnswerRaw);
1405
+ totalScore += pointsEarned;
1406
+ const timeSpentOnThisQuestion = parseFloat((this.questionTimings.get(question.id) || 0).toFixed(2));
1407
+ accumulatedTotalTimeSpent += timeSpentOnThisQuestion;
1408
+ const userAnswerDetail = this.formatUserAnswerDetail(question, userAnswerRaw);
752
1409
  questionResultsArray.push({
753
1410
  questionId: question.id,
754
1411
  questionType: question.questionType,
@@ -757,18 +1414,18 @@ var QuizEngine = class {
757
1414
  pointsEarned,
758
1415
  userAnswer: userAnswerDetail,
759
1416
  correctAnswer: correctAnswerDetail,
760
- allOptions,
761
- timeSpentSeconds: timeSpentOnThisQuestion
1417
+ timeSpentSeconds: timeSpentOnThisQuestion,
1418
+ evaluationDetails
762
1419
  });
763
1420
  }
764
1421
  const percentage = maxScore > 0 ? parseFloat((totalScore / maxScore * 100).toFixed(2)) : 0;
765
1422
  let passed = void 0;
766
- if (((_g = this.config.settings) == null ? void 0 : _g.passingScorePercent) != null) {
1423
+ if (((_b = this.config.settings) == null ? void 0 : _b.passingScorePercent) != null) {
767
1424
  passed = percentage >= this.config.settings.passingScorePercent;
768
1425
  }
769
1426
  const totalQuizTimeSpentSeconds = parseFloat(accumulatedTotalTimeSpent.toFixed(2));
770
1427
  const averageTimePerQuestionSeconds = this.questions.length > 0 ? parseFloat((totalQuizTimeSpentSeconds / this.questions.length).toFixed(2)) : 0;
771
- const metadataPerformance = this._calculateMetadataPerformance();
1428
+ const metadataPerformance = await this._calculateMetadataPerformance();
772
1429
  const finalResults = __spreadValues({
773
1430
  score: totalScore,
774
1431
  maxScore,
@@ -784,11 +1441,118 @@ var QuizEngine = class {
784
1441
  averageTimePerQuestionSeconds
785
1442
  }, metadataPerformance);
786
1443
  this.quizResultState = __spreadValues(__spreadValues({}, this.quizResultState), finalResults);
787
- if ((_h = this.config.settings) == null ? void 0 : _h.scorm) this._sendResultsToSCORM(finalResults);
1444
+ if ((_c = this.config.settings) == null ? void 0 : _c.scorm) this._sendResultsToSCORM(finalResults);
788
1445
  await this._sendResultsToWebhook(finalResults);
789
- (_j = (_i = this.callbacks).onQuizFinish) == null ? void 0 : _j.call(_i, finalResults);
1446
+ (_e = (_d = this.callbacks).onQuizFinish) == null ? void 0 : _e.call(_d, finalResults);
790
1447
  return finalResults;
791
1448
  }
1449
+ formatUserAnswerDetail(question, userAnswerRaw) {
1450
+ var _a, _b, _c, _d, _e;
1451
+ if (userAnswerRaw === null) return null;
1452
+ switch (question.questionType) {
1453
+ case "multiple_choice": {
1454
+ const q = question;
1455
+ const id = userAnswerRaw;
1456
+ return { id, value: ((_a = q.options.find((opt) => opt.id === id)) == null ? void 0 : _a.text) || "" };
1457
+ }
1458
+ case "multiple_response": {
1459
+ const q = question;
1460
+ const ids = userAnswerRaw;
1461
+ const values = ids.map((id) => {
1462
+ var _a2;
1463
+ return ((_a2 = q.options.find((opt) => opt.id === id)) == null ? void 0 : _a2.text) || "";
1464
+ });
1465
+ return { id: ids, value: values };
1466
+ }
1467
+ case "sequence": {
1468
+ const q = question;
1469
+ const ids = userAnswerRaw;
1470
+ const values = ids.map((id) => {
1471
+ var _a2;
1472
+ return ((_a2 = q.items.find((item) => item.id === id)) == null ? void 0 : _a2.content) || "";
1473
+ });
1474
+ return { id: ids, value: values };
1475
+ }
1476
+ case "matching": {
1477
+ const q = question;
1478
+ const userAnswerMap = userAnswerRaw;
1479
+ const valueMap = {};
1480
+ for (const promptId in userAnswerMap) {
1481
+ const optionId = userAnswerMap[promptId];
1482
+ const promptText = ((_b = q.prompts.find((p) => p.id === promptId)) == null ? void 0 : _b.content) || "";
1483
+ const optionText = ((_c = q.options.find((o) => o.id === optionId)) == null ? void 0 : _c.content) || "";
1484
+ valueMap[promptText] = optionText;
1485
+ }
1486
+ return { id: null, value: valueMap };
1487
+ }
1488
+ case "drag_and_drop": {
1489
+ const q = question;
1490
+ if (typeof userAnswerRaw === "object" && userAnswerRaw !== null && !Array.isArray(userAnswerRaw)) {
1491
+ const userAnswerMapByIds = userAnswerRaw;
1492
+ const enrichedUserAnswerMap = {};
1493
+ for (const draggableId in userAnswerMapByIds) {
1494
+ const dropZoneId = userAnswerMapByIds[draggableId];
1495
+ const draggableText = ((_d = q.draggableItems.find((d) => d.id === draggableId)) == null ? void 0 : _d.content) || `(ID: ${draggableId})`;
1496
+ const dropZoneText = ((_e = q.dropZones.find((z3) => z3.id === dropZoneId)) == null ? void 0 : _e.label) || `(ID: ${dropZoneId})`;
1497
+ enrichedUserAnswerMap[draggableText] = dropZoneText;
1498
+ }
1499
+ return { id: null, value: enrichedUserAnswerMap };
1500
+ }
1501
+ return { id: null, value: userAnswerRaw };
1502
+ }
1503
+ default:
1504
+ return { id: null, value: userAnswerRaw };
1505
+ }
1506
+ }
1507
+ async _calculateMetadataPerformance() {
1508
+ var _a;
1509
+ const loPerformanceMap = /* @__PURE__ */ new Map();
1510
+ const categoryPerformanceMap = /* @__PURE__ */ new Map();
1511
+ const topicPerformanceMap = /* @__PURE__ */ new Map();
1512
+ const difficultyPerformanceMap = /* @__PURE__ */ new Map();
1513
+ const bloomLevelPerformanceMap = /* @__PURE__ */ new Map();
1514
+ const updateMap = (map, key, points, isCorrect) => {
1515
+ if (!key) return;
1516
+ const current = map.get(key) || { totalQuestions: 0, correctQuestions: 0, pointsEarned: 0, maxPoints: 0 };
1517
+ current.totalQuestions++;
1518
+ current.maxPoints += points;
1519
+ if (isCorrect) {
1520
+ current.correctQuestions++;
1521
+ current.pointsEarned += points;
1522
+ }
1523
+ map.set(key, current);
1524
+ };
1525
+ for (const q of this.questions) {
1526
+ const userAnswer = this.userAnswers.get(q.id) || null;
1527
+ const evaluator = this.evaluators.get(q.questionType);
1528
+ if (evaluator) {
1529
+ const { isCorrect } = await evaluator.evaluate(q, userAnswer);
1530
+ const pointsForThisQuestion = (_a = q.points) != null ? _a : 0;
1531
+ updateMap(loPerformanceMap, q.learningObjective, pointsForThisQuestion, isCorrect);
1532
+ updateMap(categoryPerformanceMap, q.category, pointsForThisQuestion, isCorrect);
1533
+ updateMap(topicPerformanceMap, q.topic, pointsForThisQuestion, isCorrect);
1534
+ updateMap(difficultyPerformanceMap, q.difficulty, pointsForThisQuestion, isCorrect);
1535
+ updateMap(bloomLevelPerformanceMap, q.bloomLevel, pointsForThisQuestion, isCorrect);
1536
+ }
1537
+ }
1538
+ const formatPerformanceArray = (map, keyName) => {
1539
+ return Array.from(map.entries()).map(([key, data]) => ({
1540
+ [keyName]: key,
1541
+ totalQuestions: data.totalQuestions,
1542
+ correctQuestions: data.correctQuestions,
1543
+ pointsEarned: data.pointsEarned,
1544
+ maxPoints: data.maxPoints,
1545
+ percentage: data.maxPoints > 0 ? parseFloat((data.pointsEarned / data.maxPoints * 100).toFixed(2)) : 0
1546
+ }));
1547
+ };
1548
+ return {
1549
+ performanceByLearningObjective: formatPerformanceArray(loPerformanceMap, "learningObjective"),
1550
+ performanceByCategory: formatPerformanceArray(categoryPerformanceMap, "category"),
1551
+ performanceByTopic: formatPerformanceArray(topicPerformanceMap, "topic"),
1552
+ performanceByDifficulty: formatPerformanceArray(difficultyPerformanceMap, "difficulty"),
1553
+ performanceByBloomLevel: formatPerformanceArray(bloomLevelPerformanceMap, "bloomLevel")
1554
+ };
1555
+ }
792
1556
  async _sendResultsToWebhook(results) {
793
1557
  var _a;
794
1558
  if (!((_a = this.config.settings) == null ? void 0 : _a.webhookUrl)) {
@@ -856,80 +1620,15 @@ var QuizEngine = class {
856
1620
  results.scormError = e instanceof Error ? e.message : "Unknown SCORM data sending error.";
857
1621
  }
858
1622
  }
859
- _calculateMetadataPerformance() {
860
- const loPerformanceMap = /* @__PURE__ */ new Map();
861
- const categoryPerformanceMap = /* @__PURE__ */ new Map();
862
- const topicPerformanceMap = /* @__PURE__ */ new Map();
863
- const difficultyPerformanceMap = /* @__PURE__ */ new Map();
864
- const bloomLevelPerformanceMap = /* @__PURE__ */ new Map();
865
- const updateMap = (map, key, points, isCorrect) => {
866
- if (!key) return;
867
- const current = map.get(key) || { totalQuestions: 0, correctQuestions: 0, pointsEarned: 0, maxPoints: 0 };
868
- current.totalQuestions++;
869
- current.maxPoints += points;
870
- if (isCorrect) {
871
- current.correctQuestions++;
872
- current.pointsEarned += points;
873
- }
874
- map.set(key, current);
875
- };
876
- this.questions.forEach((q) => {
877
- var _a;
878
- const qResult = this.userAnswers.get(q.id);
879
- const { isCorrect } = this.evaluateQuestion(q, qResult || null);
880
- const pointsForThisQuestion = (_a = q.points) != null ? _a : 0;
881
- updateMap(loPerformanceMap, q.learningObjective, pointsForThisQuestion, isCorrect);
882
- updateMap(categoryPerformanceMap, q.category, pointsForThisQuestion, isCorrect);
883
- updateMap(topicPerformanceMap, q.topic, pointsForThisQuestion, isCorrect);
884
- updateMap(difficultyPerformanceMap, q.difficulty, pointsForThisQuestion, isCorrect);
885
- updateMap(bloomLevelPerformanceMap, q.bloomLevel, pointsForThisQuestion, isCorrect);
886
- });
887
- const formatPerformanceArray = (map, keyName) => {
888
- return Array.from(map.entries()).map(([key, data]) => ({
889
- [keyName]: key,
890
- totalQuestions: data.totalQuestions,
891
- correctQuestions: data.correctQuestions,
892
- pointsEarned: data.pointsEarned,
893
- maxPoints: data.maxPoints,
894
- percentage: data.maxPoints > 0 ? parseFloat((data.pointsEarned / data.maxPoints * 100).toFixed(2)) : 0
895
- }));
896
- };
897
- return {
898
- performanceByLearningObjective: formatPerformanceArray(loPerformanceMap, "learningObjective"),
899
- performanceByCategory: formatPerformanceArray(categoryPerformanceMap, "category"),
900
- performanceByTopic: formatPerformanceArray(topicPerformanceMap, "topic"),
901
- performanceByDifficulty: formatPerformanceArray(difficultyPerformanceMap, "difficulty"),
902
- performanceByBloomLevel: formatPerformanceArray(bloomLevelPerformanceMap, "bloomLevel")
903
- };
904
- }
905
- getElapsedTime() {
906
- return Date.now() - this.overallStartTime;
907
- }
908
- destroy() {
909
- this.stopTimer();
910
- this._recordCurrentQuestionTime();
911
- if (this.scormService && this.scormService.hasAPI()) {
912
- if (["initialized", "committed", "sending_data"].includes(this.quizResultState.scormStatus || "")) {
913
- const termResult = this.scormService.terminate();
914
- if (termResult.success) {
915
- this.quizResultState.scormStatus = "terminated";
916
- } else {
917
- this.quizResultState.scormStatus = "error";
918
- this.quizResultState.scormError = termResult.error || "SCORM termination failed on destroy.";
919
- }
920
- }
921
- }
922
- this.scormService = null;
923
- }
924
1623
  };
925
1624
  function cn(...inputs) {
926
1625
  return tailwindMerge.twMerge(clsx.clsx(inputs));
927
1626
  }
928
1627
 
929
1628
  // src/react-ui/components/elements/radio-group.tsx
930
- var RadioGroup = React25__namespace.forwardRef((_a, ref) => {
1629
+ var RadioGroup = React28__namespace.forwardRef((_a, ref) => {
931
1630
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
932
- return /* @__PURE__ */ React25__namespace.createElement(
1631
+ return /* @__PURE__ */ React28__namespace.createElement(
933
1632
  RadioGroupPrimitive__namespace.Root,
934
1633
  __spreadProps(__spreadValues({
935
1634
  className: cn("grid gap-2", className)
@@ -939,9 +1638,9 @@ var RadioGroup = React25__namespace.forwardRef((_a, ref) => {
939
1638
  );
940
1639
  });
941
1640
  RadioGroup.displayName = RadioGroupPrimitive__namespace.Root.displayName;
942
- var RadioGroupItem = React25__namespace.forwardRef((_a, ref) => {
1641
+ var RadioGroupItem = React28__namespace.forwardRef((_a, ref) => {
943
1642
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
944
- return /* @__PURE__ */ React25__namespace.createElement(
1643
+ return /* @__PURE__ */ React28__namespace.createElement(
945
1644
  RadioGroupPrimitive__namespace.Item,
946
1645
  __spreadValues({
947
1646
  ref,
@@ -950,16 +1649,16 @@ var RadioGroupItem = React25__namespace.forwardRef((_a, ref) => {
950
1649
  className
951
1650
  )
952
1651
  }, props),
953
- /* @__PURE__ */ React25__namespace.createElement(RadioGroupPrimitive__namespace.Indicator, { className: "flex items-center justify-center" }, /* @__PURE__ */ React25__namespace.createElement(lucideReact.Circle, { className: "h-2.5 w-2.5 fill-current text-current" }))
1652
+ /* @__PURE__ */ React28__namespace.createElement(RadioGroupPrimitive__namespace.Indicator, { className: "flex items-center justify-center" }, /* @__PURE__ */ React28__namespace.createElement(lucideReact.Circle, { className: "h-2.5 w-2.5 fill-current text-current" }))
954
1653
  );
955
1654
  });
956
1655
  RadioGroupItem.displayName = RadioGroupPrimitive__namespace.Item.displayName;
957
1656
  var labelVariants = classVarianceAuthority.cva(
958
1657
  "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
959
1658
  );
960
- var Label = React25__namespace.forwardRef((_a, ref) => {
1659
+ var Label = React28__namespace.forwardRef((_a, ref) => {
961
1660
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
962
- return /* @__PURE__ */ React25__namespace.createElement(
1661
+ return /* @__PURE__ */ React28__namespace.createElement(
963
1662
  LabelPrimitive__namespace.Root,
964
1663
  __spreadValues({
965
1664
  ref,
@@ -968,9 +1667,9 @@ var Label = React25__namespace.forwardRef((_a, ref) => {
968
1667
  );
969
1668
  });
970
1669
  Label.displayName = LabelPrimitive__namespace.Root.displayName;
971
- var Card = React25__namespace.forwardRef((_a, ref) => {
1670
+ var Card = React28__namespace.forwardRef((_a, ref) => {
972
1671
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
973
- return /* @__PURE__ */ React25__namespace.createElement(
1672
+ return /* @__PURE__ */ React28__namespace.createElement(
974
1673
  "div",
975
1674
  __spreadValues({
976
1675
  ref,
@@ -982,9 +1681,9 @@ var Card = React25__namespace.forwardRef((_a, ref) => {
982
1681
  );
983
1682
  });
984
1683
  Card.displayName = "Card";
985
- var CardHeader = React25__namespace.forwardRef((_a, ref) => {
1684
+ var CardHeader = React28__namespace.forwardRef((_a, ref) => {
986
1685
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
987
- return /* @__PURE__ */ React25__namespace.createElement(
1686
+ return /* @__PURE__ */ React28__namespace.createElement(
988
1687
  "div",
989
1688
  __spreadValues({
990
1689
  ref,
@@ -993,9 +1692,9 @@ var CardHeader = React25__namespace.forwardRef((_a, ref) => {
993
1692
  );
994
1693
  });
995
1694
  CardHeader.displayName = "CardHeader";
996
- var CardTitle = React25__namespace.forwardRef((_a, ref) => {
1695
+ var CardTitle = React28__namespace.forwardRef((_a, ref) => {
997
1696
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
998
- return /* @__PURE__ */ React25__namespace.createElement(
1697
+ return /* @__PURE__ */ React28__namespace.createElement(
999
1698
  "div",
1000
1699
  __spreadValues({
1001
1700
  ref,
@@ -1007,9 +1706,9 @@ var CardTitle = React25__namespace.forwardRef((_a, ref) => {
1007
1706
  );
1008
1707
  });
1009
1708
  CardTitle.displayName = "CardTitle";
1010
- var CardDescription = React25__namespace.forwardRef((_a, ref) => {
1709
+ var CardDescription = React28__namespace.forwardRef((_a, ref) => {
1011
1710
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1012
- return /* @__PURE__ */ React25__namespace.createElement(
1711
+ return /* @__PURE__ */ React28__namespace.createElement(
1013
1712
  "div",
1014
1713
  __spreadValues({
1015
1714
  ref,
@@ -1018,14 +1717,14 @@ var CardDescription = React25__namespace.forwardRef((_a, ref) => {
1018
1717
  );
1019
1718
  });
1020
1719
  CardDescription.displayName = "CardDescription";
1021
- var CardContent = React25__namespace.forwardRef((_a, ref) => {
1720
+ var CardContent = React28__namespace.forwardRef((_a, ref) => {
1022
1721
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1023
- return /* @__PURE__ */ React25__namespace.createElement("div", __spreadValues({ ref, className: cn("p-6 pt-0", className) }, props));
1722
+ return /* @__PURE__ */ React28__namespace.createElement("div", __spreadValues({ ref, className: cn("p-6 pt-0", className) }, props));
1024
1723
  });
1025
1724
  CardContent.displayName = "CardContent";
1026
- var CardFooter = React25__namespace.forwardRef((_a, ref) => {
1725
+ var CardFooter = React28__namespace.forwardRef((_a, ref) => {
1027
1726
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1028
- return /* @__PURE__ */ React25__namespace.createElement(
1727
+ return /* @__PURE__ */ React28__namespace.createElement(
1029
1728
  "div",
1030
1729
  __spreadValues({
1031
1730
  ref,
@@ -1076,7 +1775,7 @@ var MarkdownRenderer = ({
1076
1775
  const processedContent = processContentForVideos(content);
1077
1776
  return (
1078
1777
  // Using Tailwind Typography for beautiful default styling of markdown content
1079
- /* @__PURE__ */ React25__namespace.default.createElement("div", { className: `prose dark:prose-invert max-w-none ${className}` }, /* @__PURE__ */ React25__namespace.default.createElement(
1778
+ /* @__PURE__ */ React28__namespace.default.createElement("div", { className: `prose dark:prose-invert max-w-none ${className}` }, /* @__PURE__ */ React28__namespace.default.createElement(
1080
1779
  ReactMarkdown__default.default,
1081
1780
  {
1082
1781
  remarkPlugins: [remarkGfm__default.default, remarkMath__default.default],
@@ -1089,7 +1788,7 @@ var MarkdownRenderer = ({
1089
1788
  const { platform, id } = getVideoId(src);
1090
1789
  if (platform && id) {
1091
1790
  const videoSrc = platform === "youtube" ? `https://www.youtube.com/embed/${id}` : `https://player.vimeo.com/video/${id}`;
1092
- return /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "aspect-w-16 aspect-h-9 my-4" }, /* @__PURE__ */ React25__namespace.default.createElement(
1791
+ return /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "aspect-w-16 aspect-h-9 my-4" }, /* @__PURE__ */ React28__namespace.default.createElement(
1093
1792
  "iframe",
1094
1793
  {
1095
1794
  src: videoSrc,
@@ -1102,7 +1801,7 @@ var MarkdownRenderer = ({
1102
1801
  }
1103
1802
  return (
1104
1803
  // eslint-disable-next-line @next/next/no-img-element
1105
- /* @__PURE__ */ React25__namespace.default.createElement(
1804
+ /* @__PURE__ */ React28__namespace.default.createElement(
1106
1805
  "img",
1107
1806
  __spreadProps(__spreadValues({}, props), {
1108
1807
  style: {
@@ -1119,12 +1818,12 @@ var MarkdownRenderer = ({
1119
1818
  // Override the default table to add responsive wrapper
1120
1819
  table: (_c) => {
1121
1820
  var _d = _c, { node } = _d, props = __objRest(_d, ["node"]);
1122
- return /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React25__namespace.default.createElement("table", __spreadProps(__spreadValues({}, props), { className: "my-4 w-full text-sm" })));
1821
+ return /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React28__namespace.default.createElement("table", __spreadProps(__spreadValues({}, props), { className: "my-4 w-full text-sm" })));
1123
1822
  },
1124
1823
  // Override default blockquote for better styling
1125
1824
  blockquote: (_e) => {
1126
1825
  var _f = _e, { node } = _f, props = __objRest(_f, ["node"]);
1127
- return /* @__PURE__ */ React25__namespace.default.createElement(
1826
+ return /* @__PURE__ */ React28__namespace.default.createElement(
1128
1827
  "blockquote",
1129
1828
  __spreadProps(__spreadValues({}, props), {
1130
1829
  className: "border-l-4 border-primary bg-muted/50 p-4 my-4 italic"
@@ -1149,7 +1848,7 @@ var MultipleChoiceQuestionUI = ({
1149
1848
  const handleSelection = (value) => {
1150
1849
  onAnswerChange(value);
1151
1850
  };
1152
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__namespace.default.createElement(
1851
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__namespace.default.createElement(
1153
1852
  RadioGroup,
1154
1853
  {
1155
1854
  value: userAnswer || void 0,
@@ -1172,17 +1871,17 @@ var MultipleChoiceQuestionUI = ({
1172
1871
  } else {
1173
1872
  itemClassName += isSelected ? " border-primary bg-primary/10" : " border-muted";
1174
1873
  }
1175
- return /* @__PURE__ */ React25__namespace.default.createElement(
1874
+ return /* @__PURE__ */ React28__namespace.default.createElement(
1176
1875
  Label,
1177
1876
  {
1178
1877
  key: option.id,
1179
1878
  htmlFor: option.id,
1180
1879
  className: itemClassName
1181
1880
  },
1182
- /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex items-center" }, /* @__PURE__ */ React25__namespace.default.createElement(RadioGroupItem, { value: option.id, id: option.id, className: "mr-3" }), /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "text-base flex-1" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: option.text })))
1881
+ /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "flex items-center" }, /* @__PURE__ */ React28__namespace.default.createElement(RadioGroupItem, { value: option.id, id: option.id, className: "mr-3" }), /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "text-base flex-1" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: option.text })))
1183
1882
  );
1184
1883
  })
1185
- ), showCorrectAnswer && explanation && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" }))));
1884
+ ), showCorrectAnswer && explanation && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" }))));
1186
1885
  };
1187
1886
  var TrueFalseQuestionUI = ({
1188
1887
  question,
@@ -1198,7 +1897,7 @@ var TrueFalseQuestionUI = ({
1198
1897
  const handleSelection = (value) => {
1199
1898
  onAnswerChange(value);
1200
1899
  };
1201
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__namespace.default.createElement(
1900
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__namespace.default.createElement(
1202
1901
  RadioGroup,
1203
1902
  {
1204
1903
  value: userAnswer || void 0,
@@ -1221,21 +1920,21 @@ var TrueFalseQuestionUI = ({
1221
1920
  } else {
1222
1921
  itemClassName += isSelected ? " border-primary bg-primary/10" : " border-muted";
1223
1922
  }
1224
- return /* @__PURE__ */ React25__namespace.default.createElement(
1923
+ return /* @__PURE__ */ React28__namespace.default.createElement(
1225
1924
  Label,
1226
1925
  {
1227
1926
  key: option.id,
1228
1927
  htmlFor: option.id,
1229
1928
  className: itemClassName
1230
1929
  },
1231
- /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex items-center" }, /* @__PURE__ */ React25__namespace.default.createElement(RadioGroupItem, { value: option.value, id: option.id, className: "mr-3" }), /* @__PURE__ */ React25__namespace.default.createElement("span", { className: "text-base" }, option.label))
1930
+ /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "flex items-center" }, /* @__PURE__ */ React28__namespace.default.createElement(RadioGroupItem, { value: option.value, id: option.id, className: "mr-3" }), /* @__PURE__ */ React28__namespace.default.createElement("span", { className: "text-base" }, option.label))
1232
1931
  );
1233
1932
  })
1234
- ), showCorrectAnswer && explanation && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" }))));
1933
+ ), showCorrectAnswer && explanation && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" }))));
1235
1934
  };
1236
- var Checkbox = React25__namespace.forwardRef((_a, ref) => {
1935
+ var Checkbox = React28__namespace.forwardRef((_a, ref) => {
1237
1936
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1238
- return /* @__PURE__ */ React25__namespace.createElement(
1937
+ return /* @__PURE__ */ React28__namespace.createElement(
1239
1938
  CheckboxPrimitive__namespace.Root,
1240
1939
  __spreadValues({
1241
1940
  ref,
@@ -1244,12 +1943,12 @@ var Checkbox = React25__namespace.forwardRef((_a, ref) => {
1244
1943
  className
1245
1944
  )
1246
1945
  }, props),
1247
- /* @__PURE__ */ React25__namespace.createElement(
1946
+ /* @__PURE__ */ React28__namespace.createElement(
1248
1947
  CheckboxPrimitive__namespace.Indicator,
1249
1948
  {
1250
1949
  className: cn("flex items-center justify-center text-current")
1251
1950
  },
1252
- /* @__PURE__ */ React25__namespace.createElement(lucideReact.Check, { className: "h-4 w-4" })
1951
+ /* @__PURE__ */ React28__namespace.createElement(lucideReact.Check, { className: "h-4 w-4" })
1253
1952
  )
1254
1953
  );
1255
1954
  });
@@ -1277,7 +1976,7 @@ var MultipleResponseQuestionUI = ({
1277
1976
  }
1278
1977
  onAnswerChange(newAnswers.length > 0 ? newAnswers : null);
1279
1978
  };
1280
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "space-y-3", role: "group", "aria-labelledby": `question-prompt-${questionId}` }, options.map((option) => {
1979
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "space-y-3", role: "group", "aria-labelledby": `question-prompt-${questionId}` }, options.map((option) => {
1281
1980
  const isSelected = Array.isArray(userAnswer) && userAnswer.includes(option.id);
1282
1981
  const isCorrectOption = correctAnswerIds.includes(option.id);
1283
1982
  let itemClassName = "p-4 rounded-lg border-2 transition-all cursor-pointer hover:border-primary flex items-center";
@@ -1290,14 +1989,14 @@ var MultipleResponseQuestionUI = ({
1290
1989
  } else {
1291
1990
  itemClassName += isSelected ? " border-primary bg-primary/10" : " border-muted";
1292
1991
  }
1293
- return /* @__PURE__ */ React25__namespace.default.createElement(
1992
+ return /* @__PURE__ */ React28__namespace.default.createElement(
1294
1993
  Label,
1295
1994
  {
1296
1995
  key: option.id,
1297
1996
  htmlFor: option.id,
1298
1997
  className: itemClassName
1299
1998
  },
1300
- /* @__PURE__ */ React25__namespace.default.createElement(
1999
+ /* @__PURE__ */ React28__namespace.default.createElement(
1301
2000
  Checkbox,
1302
2001
  {
1303
2002
  id: option.id,
@@ -1307,14 +2006,14 @@ var MultipleResponseQuestionUI = ({
1307
2006
  "aria-label": option.text.replace(/<[^>]*>?/gm, "")
1308
2007
  }
1309
2008
  ),
1310
- /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "text-base flex-1" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: option.text }))
2009
+ /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "text-base flex-1" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: option.text }))
1311
2010
  );
1312
- })), showCorrectAnswer && explanation && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" }))));
2011
+ })), showCorrectAnswer && explanation && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" }))));
1313
2012
  };
1314
- var Input = React25__namespace.forwardRef(
2013
+ var Input = React28__namespace.forwardRef(
1315
2014
  (_a, ref) => {
1316
2015
  var _b = _a, { className, type } = _b, props = __objRest(_b, ["className", "type"]);
1317
- return /* @__PURE__ */ React25__namespace.createElement(
2016
+ return /* @__PURE__ */ React28__namespace.createElement(
1318
2017
  "input",
1319
2018
  __spreadValues({
1320
2019
  type,
@@ -1348,7 +2047,7 @@ var ShortAnswerQuestionUI = ({
1348
2047
  (accAns) => isCaseSensitive ? accAns.trim() === userAnswerTrimmed : accAns.trim().toLowerCase() === userAnswerTrimmed.toLowerCase()
1349
2048
  );
1350
2049
  }
1351
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__namespace.default.createElement(Label, { htmlFor: `short-answer-input-${questionId}`, className: "sr-only" }, "Your Answer"), /* @__PURE__ */ React25__namespace.default.createElement(
2050
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__namespace.default.createElement(Label, { htmlFor: `short-answer-input-${questionId}`, className: "sr-only" }, "Your Answer"), /* @__PURE__ */ React28__namespace.default.createElement(
1352
2051
  Input,
1353
2052
  {
1354
2053
  id: `short-answer-input-${questionId}`,
@@ -1361,7 +2060,7 @@ var ShortAnswerQuestionUI = ({
1361
2060
  ${showCorrectAnswer && userAnswer ? isActuallyCorrect ? "border-green-500 focus-visible:ring-green-500" : "border-destructive focus-visible:ring-destructive" : "border-input"}
1362
2061
  `
1363
2062
  }
1364
- ), showCorrectAnswer && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 space-y-2" }, userAnswer && !isActuallyCorrect && /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm text-destructive" }, "Your answer was marked incorrect."), /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Accepted Answers:"), /* @__PURE__ */ React25__namespace.default.createElement("ul", { className: "list-disc list-inside text-sm text-accent-foreground/80" }, acceptedAnswers.map((ans, idx) => /* @__PURE__ */ React25__namespace.default.createElement("li", { key: idx }, ans))), isCaseSensitive && /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-xs text-muted-foreground mt-1" }, "(Case-sensitive)")), explanation && /* @__PURE__ */ React25__namespace.default.createElement("div", { id: `explanation-${questionId}`, className: "mt-2 p-3 bg-muted/30 border border-muted rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold" }, "Explanation:"), /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-muted-foreground" })))));
2063
+ ), showCorrectAnswer && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 space-y-2" }, userAnswer && !isActuallyCorrect && /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm text-destructive" }, "Your answer was marked incorrect."), /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Accepted Answers:"), /* @__PURE__ */ React28__namespace.default.createElement("ul", { className: "list-disc list-inside text-sm text-accent-foreground/80" }, acceptedAnswers.map((ans, idx) => /* @__PURE__ */ React28__namespace.default.createElement("li", { key: idx }, ans))), isCaseSensitive && /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-xs text-muted-foreground mt-1" }, "(Case-sensitive)")), explanation && /* @__PURE__ */ React28__namespace.default.createElement("div", { id: `explanation-${questionId}`, className: "mt-2 p-3 bg-muted/30 border border-muted rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold" }, "Explanation:"), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-muted-foreground" })))));
1365
2064
  };
1366
2065
  var NumericQuestionUI = ({
1367
2066
  question,
@@ -1384,7 +2083,7 @@ var NumericQuestionUI = ({
1384
2083
  isActuallyCorrect = tolerance !== void 0 && tolerance !== null ? Math.abs(userAnswerNum - correctAnswerValue) <= tolerance : userAnswerNum === correctAnswerValue;
1385
2084
  }
1386
2085
  }
1387
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__namespace.default.createElement(Label, { htmlFor: `numeric-input-${questionId}`, className: "sr-only" }, "Your Answer"), /* @__PURE__ */ React25__namespace.default.createElement(
2086
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__namespace.default.createElement(Label, { htmlFor: `numeric-input-${questionId}`, className: "sr-only" }, "Your Answer"), /* @__PURE__ */ React28__namespace.default.createElement(
1388
2087
  Input,
1389
2088
  {
1390
2089
  id: `numeric-input-${questionId}`,
@@ -1399,7 +2098,7 @@ var NumericQuestionUI = ({
1399
2098
  ${showCorrectAnswer && userAnswer !== null && userAnswer !== "" ? isActuallyCorrect ? "border-green-500 focus-visible:ring-green-500" : "border-destructive focus-visible:ring-destructive" : "border-input"}
1400
2099
  `
1401
2100
  }
1402
- ), showCorrectAnswer && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 space-y-2" }, userAnswer !== null && userAnswer !== "" && !isActuallyCorrect && /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm text-destructive" }, "Your answer was marked incorrect."), /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Correct Answer:"), /* @__PURE__ */ React25__namespace.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__namespace.default.createElement("div", { id: `explanation-${questionId}`, className: "mt-2 p-3 bg-muted/30 border border-muted rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold" }, "Explanation:"), /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-muted-foreground" })))));
2101
+ ), showCorrectAnswer && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 space-y-2" }, userAnswer !== null && userAnswer !== "" && !isActuallyCorrect && /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm text-destructive" }, "Your answer was marked incorrect."), /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Correct Answer:"), /* @__PURE__ */ React28__namespace.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__namespace.default.createElement("div", { id: `explanation-${questionId}`, className: "mt-2 p-3 bg-muted/30 border border-muted rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold" }, "Explanation:"), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-muted-foreground" })))));
1403
2102
  };
1404
2103
  var FillInTheBlanksQuestionUI = ({
1405
2104
  question,
@@ -1408,8 +2107,8 @@ var FillInTheBlanksQuestionUI = ({
1408
2107
  showCorrectAnswer = false
1409
2108
  }) => {
1410
2109
  const { prompt, segments, answers: correctAnswersMap, points, explanation, id: questionId, isCaseSensitive } = question;
1411
- const [userInputs, setUserInputs] = React25.useState({});
1412
- React25.useEffect(() => {
2110
+ const [userInputs, setUserInputs] = React28.useState({});
2111
+ React28.useEffect(() => {
1413
2112
  if (userAnswer && typeof userAnswer === "object" && !Array.isArray(userAnswer)) {
1414
2113
  setUserInputs(userAnswer);
1415
2114
  } else {
@@ -1439,10 +2138,10 @@ var FillInTheBlanksQuestionUI = ({
1439
2138
  (accVal) => caseSensitive ? accVal.trim() === userAnswerForBlank : accVal.trim().toLowerCase() === userAnswerForBlank.toLowerCase()
1440
2139
  );
1441
2140
  };
1442
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__namespace.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) => {
2141
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__namespace.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) => {
1443
2142
  var _a;
1444
2143
  if (segment.type === "text") {
1445
- return /* @__PURE__ */ React25__namespace.default.createElement(
2144
+ return /* @__PURE__ */ React28__namespace.default.createElement(
1446
2145
  MarkdownRenderer,
1447
2146
  {
1448
2147
  key: `text-${index}`,
@@ -1460,7 +2159,7 @@ var FillInTheBlanksQuestionUI = ({
1460
2159
  } else {
1461
2160
  inputClassName += " border-input";
1462
2161
  }
1463
- return /* @__PURE__ */ React25__namespace.default.createElement(
2162
+ return /* @__PURE__ */ React28__namespace.default.createElement(
1464
2163
  Input,
1465
2164
  {
1466
2165
  key: blankId,
@@ -1476,11 +2175,11 @@ var FillInTheBlanksQuestionUI = ({
1476
2175
  );
1477
2176
  }
1478
2177
  return null;
1479
- })), showCorrectAnswer && explanation && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-6 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Gi\u1EA3i th\xEDch chung:"), /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 space-y-3" }, correctAnswersMap.map((ansDef) => {
2178
+ })), showCorrectAnswer && explanation && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-6 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Gi\u1EA3i th\xEDch chung:"), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 space-y-3" }, correctAnswersMap.map((ansDef) => {
1480
2179
  var _a;
1481
2180
  const isBlankCorrect = getCorrectnessForBlank(ansDef.blankId);
1482
2181
  const userAnswerDisplay = userInputs[ansDef.blankId] || "Ch\u01B0a tr\u1EA3 l\u1EDDi";
1483
- return /* @__PURE__ */ React25__namespace.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__namespace.default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React25__namespace.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__namespace.default.createElement("p", { className: "text-xs" }, "\u0110\xE1p \xE1n ch\u1EA5p nh\u1EADn: ", ansDef.acceptedValues.join(", ")));
2182
+ return /* @__PURE__ */ React28__namespace.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__namespace.default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React28__namespace.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__namespace.default.createElement("p", { className: "text-xs" }, "\u0110\xE1p \xE1n ch\u1EA5p nh\u1EADn: ", ansDef.acceptedValues.join(", ")));
1484
2183
  }))));
1485
2184
  };
1486
2185
  var buttonVariants = classVarianceAuthority.cva(
@@ -1508,11 +2207,11 @@ var buttonVariants = classVarianceAuthority.cva(
1508
2207
  }
1509
2208
  }
1510
2209
  );
1511
- var Button = React25__namespace.forwardRef(
2210
+ var Button = React28__namespace.forwardRef(
1512
2211
  (_a, ref) => {
1513
2212
  var _b = _a, { className, variant, size, asChild = false } = _b, props = __objRest(_b, ["className", "variant", "size", "asChild"]);
1514
2213
  const Comp = asChild ? reactSlot.Slot : "button";
1515
- return /* @__PURE__ */ React25__namespace.createElement(
2214
+ return /* @__PURE__ */ React28__namespace.createElement(
1516
2215
  Comp,
1517
2216
  __spreadValues({
1518
2217
  className: cn(buttonVariants({ variant, size, className })),
@@ -1529,9 +2228,9 @@ var SequenceQuestionUI = ({
1529
2228
  showCorrectAnswer = false
1530
2229
  }) => {
1531
2230
  const { prompt, items, points, explanation, id: questionId, correctOrder } = question;
1532
- const [selectedSequence, setSelectedSequence] = React25.useState([]);
1533
- const [availableItems, setAvailableItems] = React25.useState([]);
1534
- React25.useEffect(() => {
2231
+ const [selectedSequence, setSelectedSequence] = React28.useState([]);
2232
+ const [availableItems, setAvailableItems] = React28.useState([]);
2233
+ React28.useEffect(() => {
1535
2234
  const initialUserOrder = Array.isArray(userAnswer) ? userAnswer : [];
1536
2235
  const initialSelected = [];
1537
2236
  const initialAvailable = [...items];
@@ -1572,37 +2271,37 @@ var SequenceQuestionUI = ({
1572
2271
  if (!showCorrectAnswer || !Array.isArray(userAnswer) || userAnswer.length <= index) return null;
1573
2272
  const userItemId = userAnswer[index];
1574
2273
  const correctItemId = correctOrder[index];
1575
- return userItemId === correctItemId ? /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.CheckCircle, { className: "h-4 w-4 text-green-500 ml-2 flex-shrink-0" }) : /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.XCircle, { className: "h-4 w-4 text-destructive ml-2 flex-shrink-0" });
2274
+ return userItemId === correctItemId ? /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.CheckCircle, { className: "h-4 w-4 text-green-500 ml-2 flex-shrink-0" }) : /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.XCircle, { className: "h-4 w-4 text-destructive ml-2 flex-shrink-0" });
1576
2275
  };
1577
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "p-0 space-y-6" }, /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React25__namespace.default.createElement(Label, { className: "font-semibold" }, "S\u1EAFp x\u1EBFp c\xE1c m\u1EE5c sau theo \u0111\xFAng th\u1EE9 t\u1EF1:"), /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2" }, availableItems.map((item) => /* @__PURE__ */ React25__namespace.default.createElement(
2276
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0 space-y-6" }, /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React28__namespace.default.createElement(Label, { className: "font-semibold" }, "S\u1EAFp x\u1EBFp c\xE1c m\u1EE5c sau theo \u0111\xFAng th\u1EE9 t\u1EF1:"), /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2" }, availableItems.map((item) => /* @__PURE__ */ React28__namespace.default.createElement(
1578
2277
  Button,
1579
2278
  {
1580
2279
  key: item.id,
1581
2280
  variant: "outline",
1582
2281
  onClick: () => handleSelectItem(item),
1583
- className: "justify-start text-left h-auto py-2 px-3",
2282
+ className: "justify-start text-left h-auto py-2 px-3 whitespace-normal",
1584
2283
  disabled: showCorrectAnswer
1585
2284
  },
1586
- /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: item.content })
1587
- ))), availableItems.length === 0 && selectedSequence.length > 0 && /* @__PURE__ */ React25__namespace.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__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React25__namespace.default.createElement(Label, { className: "font-semibold" }, "Th\u1EE9 t\u1EF1 b\u1EA1n \u0111\xE3 ch\u1ECDn:"), /* @__PURE__ */ React25__namespace.default.createElement(Button, { variant: "ghost", size: "sm", onClick: handleResetSequence, disabled: showCorrectAnswer || selectedSequence.length === 0 }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.RotateCcw, { className: "mr-2 h-3.5 w-3.5" }), " \u0110\u1EB7t l\u1EA1i")), selectedSequence.length === 0 ? /* @__PURE__ */ React25__namespace.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__namespace.default.createElement("ul", { className: "space-y-2" }, selectedSequence.map((item, index) => /* @__PURE__ */ React25__namespace.default.createElement(
2285
+ /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: item.content })
2286
+ ))), availableItems.length === 0 && selectedSequence.length > 0 && /* @__PURE__ */ React28__namespace.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__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React28__namespace.default.createElement(Label, { className: "font-semibold" }, "Th\u1EE9 t\u1EF1 b\u1EA1n \u0111\xE3 ch\u1ECDn:"), /* @__PURE__ */ React28__namespace.default.createElement(Button, { variant: "ghost", size: "sm", onClick: handleResetSequence, disabled: showCorrectAnswer || selectedSequence.length === 0 }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.RotateCcw, { className: "mr-2 h-3.5 w-3.5" }), " \u0110\u1EB7t l\u1EA1i")), selectedSequence.length === 0 ? /* @__PURE__ */ React28__namespace.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__namespace.default.createElement("ul", { className: "space-y-2" }, selectedSequence.map((item, index) => /* @__PURE__ */ React28__namespace.default.createElement(
1588
2287
  "li",
1589
2288
  {
1590
2289
  key: item.id,
1591
2290
  onClick: () => handleRemoveFromSequence(item, index),
1592
- 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`
2291
+ 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`
1593
2292
  },
1594
- /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex-grow flex items-center" }, /* @__PURE__ */ React25__namespace.default.createElement("span", { className: "font-semibold mr-2" }, index + 1, "."), /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: item.content })),
1595
- showCorrectAnswer ? getFeedbackIcon(index) : /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.XCircle, { className: "h-4 w-4 text-muted-foreground hover:text-destructive flex-shrink-0" })
1596
- )))), showCorrectAnswer && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Th\u1EE9 t\u1EF1 \u0111\xFAng:"), /* @__PURE__ */ React25__namespace.default.createElement("ol", { className: "list-decimal list-inside text-sm text-accent-foreground/80 space-y-1 mt-1" }, correctOrder.map((itemId) => {
2293
+ /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "flex-grow flex items-center" }, /* @__PURE__ */ React28__namespace.default.createElement("span", { className: "font-semibold mr-2" }, index + 1, "."), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: item.content })),
2294
+ showCorrectAnswer ? getFeedbackIcon(index) : /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.XCircle, { className: "h-4 w-4 text-muted-foreground hover:text-destructive flex-shrink-0" })
2295
+ )))), showCorrectAnswer && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Th\u1EE9 t\u1EF1 \u0111\xFAng:"), /* @__PURE__ */ React28__namespace.default.createElement("ol", { className: "list-decimal list-inside text-sm text-accent-foreground/80 space-y-1 mt-1" }, correctOrder.map((itemId) => {
1597
2296
  const item = items.find((i) => i.id === itemId);
1598
- return /* @__PURE__ */ React25__namespace.default.createElement("li", { key: itemId }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: item ? item.content : "Kh\xF4ng t\xECm th\u1EA5y m\u1EE5c" }));
1599
- }))), explanation && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-2 p-3 bg-muted/30 border border-muted rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold" }, "Gi\u1EA3i th\xEDch:"), /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-muted-foreground" })))));
2297
+ return /* @__PURE__ */ React28__namespace.default.createElement("li", { key: itemId }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: item ? item.content : "Kh\xF4ng t\xECm th\u1EA5y m\u1EE5c" }));
2298
+ }))), explanation && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-2 p-3 bg-muted/30 border border-muted rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold" }, "Gi\u1EA3i th\xEDch:"), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-muted-foreground" })))));
1600
2299
  };
1601
2300
  var Select = SelectPrimitive__namespace.Root;
1602
2301
  var SelectValue = SelectPrimitive__namespace.Value;
1603
- var SelectTrigger = React25__namespace.forwardRef((_a, ref) => {
2302
+ var SelectTrigger = React28__namespace.forwardRef((_a, ref) => {
1604
2303
  var _b = _a, { className, children } = _b, props = __objRest(_b, ["className", "children"]);
1605
- return /* @__PURE__ */ React25__namespace.createElement(
2304
+ return /* @__PURE__ */ React28__namespace.createElement(
1606
2305
  SelectPrimitive__namespace.Trigger,
1607
2306
  __spreadValues({
1608
2307
  ref,
@@ -1612,13 +2311,13 @@ var SelectTrigger = React25__namespace.forwardRef((_a, ref) => {
1612
2311
  )
1613
2312
  }, props),
1614
2313
  children,
1615
- /* @__PURE__ */ React25__namespace.createElement(SelectPrimitive__namespace.Icon, { asChild: true }, /* @__PURE__ */ React25__namespace.createElement(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" }))
2314
+ /* @__PURE__ */ React28__namespace.createElement(SelectPrimitive__namespace.Icon, { asChild: true }, /* @__PURE__ */ React28__namespace.createElement(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" }))
1616
2315
  );
1617
2316
  });
1618
2317
  SelectTrigger.displayName = SelectPrimitive__namespace.Trigger.displayName;
1619
- var SelectScrollUpButton = React25__namespace.forwardRef((_a, ref) => {
2318
+ var SelectScrollUpButton = React28__namespace.forwardRef((_a, ref) => {
1620
2319
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1621
- return /* @__PURE__ */ React25__namespace.createElement(
2320
+ return /* @__PURE__ */ React28__namespace.createElement(
1622
2321
  SelectPrimitive__namespace.ScrollUpButton,
1623
2322
  __spreadValues({
1624
2323
  ref,
@@ -1627,13 +2326,13 @@ var SelectScrollUpButton = React25__namespace.forwardRef((_a, ref) => {
1627
2326
  className
1628
2327
  )
1629
2328
  }, props),
1630
- /* @__PURE__ */ React25__namespace.createElement(lucideReact.ChevronUp, { className: "h-4 w-4" })
2329
+ /* @__PURE__ */ React28__namespace.createElement(lucideReact.ChevronUp, { className: "h-4 w-4" })
1631
2330
  );
1632
2331
  });
1633
2332
  SelectScrollUpButton.displayName = SelectPrimitive__namespace.ScrollUpButton.displayName;
1634
- var SelectScrollDownButton = React25__namespace.forwardRef((_a, ref) => {
2333
+ var SelectScrollDownButton = React28__namespace.forwardRef((_a, ref) => {
1635
2334
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1636
- return /* @__PURE__ */ React25__namespace.createElement(
2335
+ return /* @__PURE__ */ React28__namespace.createElement(
1637
2336
  SelectPrimitive__namespace.ScrollDownButton,
1638
2337
  __spreadValues({
1639
2338
  ref,
@@ -1642,13 +2341,13 @@ var SelectScrollDownButton = React25__namespace.forwardRef((_a, ref) => {
1642
2341
  className
1643
2342
  )
1644
2343
  }, props),
1645
- /* @__PURE__ */ React25__namespace.createElement(lucideReact.ChevronDown, { className: "h-4 w-4" })
2344
+ /* @__PURE__ */ React28__namespace.createElement(lucideReact.ChevronDown, { className: "h-4 w-4" })
1646
2345
  );
1647
2346
  });
1648
2347
  SelectScrollDownButton.displayName = SelectPrimitive__namespace.ScrollDownButton.displayName;
1649
- var SelectContent = React25__namespace.forwardRef((_a, ref) => {
2348
+ var SelectContent = React28__namespace.forwardRef((_a, ref) => {
1650
2349
  var _b = _a, { className, children, position = "popper" } = _b, props = __objRest(_b, ["className", "children", "position"]);
1651
- return /* @__PURE__ */ React25__namespace.createElement(SelectPrimitive__namespace.Portal, null, /* @__PURE__ */ React25__namespace.createElement(
2350
+ return /* @__PURE__ */ React28__namespace.createElement(SelectPrimitive__namespace.Portal, null, /* @__PURE__ */ React28__namespace.createElement(
1652
2351
  SelectPrimitive__namespace.Content,
1653
2352
  __spreadValues({
1654
2353
  ref,
@@ -1659,8 +2358,8 @@ var SelectContent = React25__namespace.forwardRef((_a, ref) => {
1659
2358
  ),
1660
2359
  position
1661
2360
  }, props),
1662
- /* @__PURE__ */ React25__namespace.createElement(SelectScrollUpButton, null),
1663
- /* @__PURE__ */ React25__namespace.createElement(
2361
+ /* @__PURE__ */ React28__namespace.createElement(SelectScrollUpButton, null),
2362
+ /* @__PURE__ */ React28__namespace.createElement(
1664
2363
  SelectPrimitive__namespace.Viewport,
1665
2364
  {
1666
2365
  className: cn(
@@ -1670,13 +2369,13 @@ var SelectContent = React25__namespace.forwardRef((_a, ref) => {
1670
2369
  },
1671
2370
  children
1672
2371
  ),
1673
- /* @__PURE__ */ React25__namespace.createElement(SelectScrollDownButton, null)
2372
+ /* @__PURE__ */ React28__namespace.createElement(SelectScrollDownButton, null)
1674
2373
  ));
1675
2374
  });
1676
2375
  SelectContent.displayName = SelectPrimitive__namespace.Content.displayName;
1677
- var SelectLabel = React25__namespace.forwardRef((_a, ref) => {
2376
+ var SelectLabel = React28__namespace.forwardRef((_a, ref) => {
1678
2377
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1679
- return /* @__PURE__ */ React25__namespace.createElement(
2378
+ return /* @__PURE__ */ React28__namespace.createElement(
1680
2379
  SelectPrimitive__namespace.Label,
1681
2380
  __spreadValues({
1682
2381
  ref,
@@ -1685,9 +2384,9 @@ var SelectLabel = React25__namespace.forwardRef((_a, ref) => {
1685
2384
  );
1686
2385
  });
1687
2386
  SelectLabel.displayName = SelectPrimitive__namespace.Label.displayName;
1688
- var SelectItem = React25__namespace.forwardRef((_a, ref) => {
2387
+ var SelectItem = React28__namespace.forwardRef((_a, ref) => {
1689
2388
  var _b = _a, { className, children } = _b, props = __objRest(_b, ["className", "children"]);
1690
- return /* @__PURE__ */ React25__namespace.createElement(
2389
+ return /* @__PURE__ */ React28__namespace.createElement(
1691
2390
  SelectPrimitive__namespace.Item,
1692
2391
  __spreadValues({
1693
2392
  ref,
@@ -1696,14 +2395,14 @@ var SelectItem = React25__namespace.forwardRef((_a, ref) => {
1696
2395
  className
1697
2396
  )
1698
2397
  }, props),
1699
- /* @__PURE__ */ React25__namespace.createElement("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" }, /* @__PURE__ */ React25__namespace.createElement(SelectPrimitive__namespace.ItemIndicator, null, /* @__PURE__ */ React25__namespace.createElement(lucideReact.Check, { className: "h-4 w-4" }))),
1700
- /* @__PURE__ */ React25__namespace.createElement(SelectPrimitive__namespace.ItemText, null, children)
2398
+ /* @__PURE__ */ React28__namespace.createElement("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center" }, /* @__PURE__ */ React28__namespace.createElement(SelectPrimitive__namespace.ItemIndicator, null, /* @__PURE__ */ React28__namespace.createElement(lucideReact.Check, { className: "h-4 w-4" }))),
2399
+ /* @__PURE__ */ React28__namespace.createElement(SelectPrimitive__namespace.ItemText, null, children)
1701
2400
  );
1702
2401
  });
1703
2402
  SelectItem.displayName = SelectPrimitive__namespace.Item.displayName;
1704
- var SelectSeparator = React25__namespace.forwardRef((_a, ref) => {
2403
+ var SelectSeparator = React28__namespace.forwardRef((_a, ref) => {
1705
2404
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
1706
- return /* @__PURE__ */ React25__namespace.createElement(
2405
+ return /* @__PURE__ */ React28__namespace.createElement(
1707
2406
  SelectPrimitive__namespace.Separator,
1708
2407
  __spreadValues({
1709
2408
  ref,
@@ -1719,16 +2418,16 @@ var MatchingQuestionUI = ({
1719
2418
  showCorrectAnswer = false
1720
2419
  }) => {
1721
2420
  const { prompt, prompts, options: initialOptions, points, explanation, correctAnswerMap, id: questionId } = question;
1722
- const [currentAnswers, setCurrentAnswers] = React25.useState({});
1723
- const [shuffledOptions, setShuffledOptions] = React25.useState(initialOptions);
1724
- React25.useEffect(() => {
2421
+ const [currentAnswers, setCurrentAnswers] = React28.useState({});
2422
+ const [shuffledOptions, setShuffledOptions] = React28.useState(initialOptions);
2423
+ React28.useEffect(() => {
1725
2424
  if (question.shuffleOptions) {
1726
2425
  setShuffledOptions([...initialOptions].sort(() => Math.random() - 0.5));
1727
2426
  } else {
1728
2427
  setShuffledOptions(initialOptions);
1729
2428
  }
1730
2429
  }, [initialOptions, question.shuffleOptions]);
1731
- React25.useEffect(() => {
2430
+ React28.useEffect(() => {
1732
2431
  if (userAnswer && typeof userAnswer === "object" && !Array.isArray(userAnswer)) {
1733
2432
  setCurrentAnswers(userAnswer);
1734
2433
  } else {
@@ -1756,7 +2455,7 @@ var MatchingQuestionUI = ({
1756
2455
  }
1757
2456
  return htmlString.replace(/<[^>]*>?/gm, "");
1758
2457
  };
1759
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "p-0 space-y-4" }, /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-4" }, prompts.map((promptItem) => {
2458
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0 space-y-4" }, /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-4" }, prompts.map((promptItem) => {
1760
2459
  var _a;
1761
2460
  const selectedOptionId = currentAnswers[promptItem.id] || "";
1762
2461
  const correctOptionId = getCorrectOptionIdForPrompt(promptItem.id);
@@ -1765,21 +2464,21 @@ var MatchingQuestionUI = ({
1765
2464
  if (showCorrectAnswer && selectedOptionId) {
1766
2465
  borderColor = isSelectionCorrect ? "border-green-500" : "border-destructive";
1767
2466
  }
1768
- return /* @__PURE__ */ React25__namespace.default.createElement("div", { key: promptItem.id, className: `p-3 border rounded-md ${borderColor} transition-colors bg-background` }, /* @__PURE__ */ React25__namespace.default.createElement(Label, { htmlFor: `select-prompt-${promptItem.id}`, className: "font-medium text-base block mb-2" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: promptItem.content })), /* @__PURE__ */ React25__namespace.default.createElement(
2467
+ return /* @__PURE__ */ React28__namespace.default.createElement("div", { key: promptItem.id, className: `p-3 border rounded-md ${borderColor} transition-colors bg-background` }, /* @__PURE__ */ React28__namespace.default.createElement(Label, { htmlFor: `select-prompt-${promptItem.id}`, className: "font-medium text-base block mb-2 whitespace-normal" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: promptItem.content })), /* @__PURE__ */ React28__namespace.default.createElement(
1769
2468
  Select,
1770
2469
  {
1771
2470
  value: selectedOptionId,
1772
2471
  onValueChange: (value) => handleSelectChange(promptItem.id, value),
1773
2472
  disabled: showCorrectAnswer
1774
2473
  },
1775
- /* @__PURE__ */ React25__namespace.default.createElement(SelectTrigger, { id: `select-prompt-${promptItem.id}` }, /* @__PURE__ */ React25__namespace.default.createElement(SelectValue, { placeholder: "Ch\u1ECDn \u0111\xE1p \xE1n..." })),
1776
- /* @__PURE__ */ React25__namespace.default.createElement(SelectContent, null, shuffledOptions.map((option) => /* @__PURE__ */ React25__namespace.default.createElement(SelectItem, { key: option.id, value: option.id }, getPlainText(option.content))))
1777
- ), showCorrectAnswer && selectedOptionId && (isSelectionCorrect ? /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.CheckCircle, { className: "h-5 w-5 text-green-500 mt-2 inline-block" }) : /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.XCircle, { className: "h-5 w-5 text-destructive mt-2 inline-block" })), showCorrectAnswer && !selectedOptionId && correctOptionId && /* @__PURE__ */ React25__namespace.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)));
1778
- })), showCorrectAnswer && explanation && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-6 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Gi\u1EA3i th\xEDch:"), /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "font-semibold text-md" }, "\u0110\xE1p \xE1n \u0111\xFAng:"), /* @__PURE__ */ React25__namespace.default.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" }, correctAnswerMap.map((map) => {
2474
+ /* @__PURE__ */ React28__namespace.default.createElement(SelectTrigger, { id: `select-prompt-${promptItem.id}` }, /* @__PURE__ */ React28__namespace.default.createElement(SelectValue, { placeholder: "Ch\u1ECDn \u0111\xE1p \xE1n..." })),
2475
+ /* @__PURE__ */ React28__namespace.default.createElement(SelectContent, null, shuffledOptions.map((option) => /* @__PURE__ */ React28__namespace.default.createElement(SelectItem, { key: option.id, value: option.id, className: "whitespace-normal" }, getPlainText(option.content))))
2476
+ ), showCorrectAnswer && selectedOptionId && (isSelectionCorrect ? /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.CheckCircle, { className: "h-5 w-5 text-green-500 mt-2 inline-block" }) : /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.XCircle, { className: "h-5 w-5 text-destructive mt-2 inline-block" })), showCorrectAnswer && !selectedOptionId && correctOptionId && /* @__PURE__ */ React28__namespace.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)));
2477
+ })), showCorrectAnswer && explanation && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-6 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Gi\u1EA3i th\xEDch:"), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "font-semibold text-md" }, "\u0110\xE1p \xE1n \u0111\xFAng:"), /* @__PURE__ */ React28__namespace.default.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" }, correctAnswerMap.map((map) => {
1779
2478
  var _a, _b;
1780
2479
  const promptText = (_a = prompts.find((p) => p.id === map.promptId)) == null ? void 0 : _a.content;
1781
2480
  const optionText = (_b = initialOptions.find((o) => o.id === map.optionId)) == null ? void 0 : _b.content;
1782
- return /* @__PURE__ */ React25__namespace.default.createElement("li", { key: map.promptId }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: `<strong>${getPlainText(promptText) || "N/A"}</strong> gh\xE9p v\u1EDBi <strong>${getPlainText(optionText) || "N/A"}</strong>` }));
2481
+ return /* @__PURE__ */ React28__namespace.default.createElement("li", { key: map.promptId }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: `<strong>${getPlainText(promptText) || "N/A"}</strong> gh\xE9p v\u1EDBi <strong>${getPlainText(optionText) || "N/A"}</strong>` }));
1783
2482
  })))));
1784
2483
  };
1785
2484
  var DragAndDropQuestionUI = ({
@@ -1789,8 +2488,8 @@ var DragAndDropQuestionUI = ({
1789
2488
  showCorrectAnswer = false
1790
2489
  }) => {
1791
2490
  const { prompt, draggableItems, dropZones, points, explanation, answerMap, id: questionId, backgroundImageUrl } = question;
1792
- const [currentAnswers, setCurrentAnswers] = React25.useState({});
1793
- React25.useEffect(() => {
2491
+ const [currentAnswers, setCurrentAnswers] = React28.useState({});
2492
+ React28.useEffect(() => {
1794
2493
  if (userAnswer && typeof userAnswer === "object" && !Array.isArray(userAnswer)) {
1795
2494
  setCurrentAnswers(userAnswer);
1796
2495
  } else {
@@ -1809,7 +2508,7 @@ var DragAndDropQuestionUI = ({
1809
2508
  var _a;
1810
2509
  return (_a = answerMap.find((map) => map.draggableId === draggableItemId)) == null ? void 0 : _a.dropZoneId;
1811
2510
  };
1812
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "p-0 space-y-4" }, backgroundImageUrl && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mb-4 overflow-hidden rounded-md border" }, /* @__PURE__ */ React25__namespace.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__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React25__namespace.default.createElement(Label, { className: "font-semibold" }, "Gh\xE9p c\xE1c m\u1EE5c sau v\xE0o \u0111\xFAng v\u1ECB tr\xED:"), draggableItems.map((item) => {
2511
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0 space-y-4" }, backgroundImageUrl && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mb-4 overflow-hidden rounded-md border" }, /* @__PURE__ */ React28__namespace.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__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React28__namespace.default.createElement(Label, { className: "font-semibold" }, "Gh\xE9p c\xE1c m\u1EE5c sau v\xE0o \u0111\xFAng v\u1ECB tr\xED:"), draggableItems.map((item) => {
1813
2512
  const selectedDropZoneId = currentAnswers[item.id] || "";
1814
2513
  const correctDropZoneId = getCorrectDropZoneIdForDraggable(item.id);
1815
2514
  const isSelectionCorrect = showCorrectAnswer && selectedDropZoneId ? selectedDropZoneId === correctDropZoneId : null;
@@ -1819,21 +2518,21 @@ var DragAndDropQuestionUI = ({
1819
2518
  } else {
1820
2519
  itemStyle += " border-muted";
1821
2520
  }
1822
- return /* @__PURE__ */ React25__namespace.default.createElement("div", { key: item.id, className: itemStyle }, /* @__PURE__ */ React25__namespace.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__namespace.default.createElement(MarkdownRenderer, { content: item.content })), /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex items-center space-x-2 w-full sm:w-auto" }, /* @__PURE__ */ React25__namespace.default.createElement(
2521
+ return /* @__PURE__ */ React28__namespace.default.createElement("div", { key: item.id, className: itemStyle }, /* @__PURE__ */ React28__namespace.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__namespace.default.createElement(MarkdownRenderer, { content: item.content })), /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "flex items-center space-x-2 w-full sm:w-auto" }, /* @__PURE__ */ React28__namespace.default.createElement(
1823
2522
  Select,
1824
2523
  {
1825
2524
  value: selectedDropZoneId,
1826
2525
  onValueChange: (value) => handleSelectChange(item.id, value),
1827
2526
  disabled: showCorrectAnswer
1828
2527
  },
1829
- /* @__PURE__ */ React25__namespace.default.createElement(SelectTrigger, { id: `select-draggable-${item.id}`, className: "w-full sm:min-w-[200px]" }, /* @__PURE__ */ React25__namespace.default.createElement(SelectValue, { placeholder: "Ch\u1ECDn v\u1ECB tr\xED..." })),
1830
- /* @__PURE__ */ React25__namespace.default.createElement(SelectContent, null, dropZones.map((zone) => /* @__PURE__ */ React25__namespace.default.createElement(SelectItem, { key: zone.id, value: zone.id }, zone.label.replace(/<[^>]*>?/gm, ""))))
1831
- ), showCorrectAnswer && selectedDropZoneId && (isSelectionCorrect ? /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.CheckCircle, { className: "h-5 w-5 text-green-500 flex-shrink-0" }) : /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.XCircle, { className: "h-5 w-5 text-destructive flex-shrink-0" }))));
1832
- })), showCorrectAnswer && explanation && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-6 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Gi\u1EA3i th\xEDch:"), /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "font-semibold text-md" }, "\u0110\xE1p \xE1n \u0111\xFAng:"), /* @__PURE__ */ React25__namespace.default.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" }, answerMap.map((map) => {
2528
+ /* @__PURE__ */ React28__namespace.default.createElement(SelectTrigger, { id: `select-draggable-${item.id}`, className: "w-full sm:min-w-[200px]" }, /* @__PURE__ */ React28__namespace.default.createElement(SelectValue, { placeholder: "Ch\u1ECDn v\u1ECB tr\xED..." })),
2529
+ /* @__PURE__ */ React28__namespace.default.createElement(SelectContent, null, dropZones.map((zone) => /* @__PURE__ */ React28__namespace.default.createElement(SelectItem, { key: zone.id, value: zone.id }, zone.label.replace(/<[^>]*>?/gm, ""))))
2530
+ ), showCorrectAnswer && selectedDropZoneId && (isSelectionCorrect ? /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.CheckCircle, { className: "h-5 w-5 text-green-500 flex-shrink-0" }) : /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.XCircle, { className: "h-5 w-5 text-destructive flex-shrink-0" }))));
2531
+ })), showCorrectAnswer && explanation && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-6 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Gi\u1EA3i th\xEDch:"), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "font-semibold text-md" }, "\u0110\xE1p \xE1n \u0111\xFAng:"), /* @__PURE__ */ React28__namespace.default.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" }, answerMap.map((map) => {
1833
2532
  var _a, _b;
1834
2533
  const draggableText = (_a = draggableItems.find((d) => d.id === map.draggableId)) == null ? void 0 : _a.content;
1835
- const dropZoneText = (_b = dropZones.find((z) => z.id === map.dropZoneId)) == null ? void 0 : _b.label;
1836
- return /* @__PURE__ */ React25__namespace.default.createElement("li", { key: map.draggableId }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: `<strong>${draggableText || "N/A"}</strong> v\xE0o <strong>${dropZoneText || "N/A"}</strong>` }));
2534
+ const dropZoneText = (_b = dropZones.find((z3) => z3.id === map.dropZoneId)) == null ? void 0 : _b.label;
2535
+ return /* @__PURE__ */ React28__namespace.default.createElement("li", { key: map.draggableId }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: `<strong>${draggableText || "N/A"}</strong> v\xE0o <strong>${dropZoneText || "N/A"}</strong>` }));
1837
2536
  })))));
1838
2537
  };
1839
2538
  var HotspotQuestionUI = ({
@@ -1843,8 +2542,8 @@ var HotspotQuestionUI = ({
1843
2542
  showCorrectAnswer = false
1844
2543
  }) => {
1845
2544
  const { prompt, imageUrl, imageAltText, hotspots, points, explanation, correctAnswerIds, id: questionId } = question;
1846
- const [selectedIds, setSelectedIds] = React25.useState([]);
1847
- React25.useEffect(() => {
2545
+ const [selectedIds, setSelectedIds] = React28.useState([]);
2546
+ React28.useEffect(() => {
1848
2547
  if (Array.isArray(userAnswer)) {
1849
2548
  setSelectedIds(userAnswer);
1850
2549
  } else {
@@ -1904,7 +2603,7 @@ var HotspotQuestionUI = ({
1904
2603
  tempDiv.innerHTML = htmlString;
1905
2604
  return tempDiv.textContent || tempDiv.innerText || "";
1906
2605
  };
1907
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "p-0 space-y-4" }, /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "relative w-full border border-muted rounded-md overflow-hidden", style: { maxWidth: "100%", maxHeight: "500px" } }, /* @__PURE__ */ React25__namespace.default.createElement(
2606
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: prompt })), points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0 space-y-4" }, /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "relative w-full border border-muted rounded-md overflow-hidden", style: { maxWidth: "100%", maxHeight: "500px" } }, /* @__PURE__ */ React28__namespace.default.createElement(
1908
2607
  "img",
1909
2608
  {
1910
2609
  src: imageUrl,
@@ -1912,7 +2611,7 @@ var HotspotQuestionUI = ({
1912
2611
  className: "block max-w-full max-h-full object-contain",
1913
2612
  "data-ai-hint": question.imageAltText ? question.imageAltText.split(" ").slice(0, 2).join(" ") : "diagram illustration"
1914
2613
  }
1915
- ), hotspots.map((hotspot) => /* @__PURE__ */ React25__namespace.default.createElement(
2614
+ ), hotspots.map((hotspot) => /* @__PURE__ */ React28__namespace.default.createElement(
1916
2615
  "div",
1917
2616
  {
1918
2617
  key: hotspot.id,
@@ -1926,9 +2625,9 @@ var HotspotQuestionUI = ({
1926
2625
  if (e.key === "Enter" || e.key === " ") handleHotspotClick(hotspot.id);
1927
2626
  }
1928
2627
  }
1929
- ))), showCorrectAnswer && explanation && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "font-semibold text-md" }, "Correct Hotspots:"), /* @__PURE__ */ React25__namespace.default.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" }, (correctAnswerIds || []).map((id) => {
2628
+ ))), showCorrectAnswer && explanation && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: explanation, className: "text-sm text-accent-foreground/80" })), showCorrectAnswer && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 space-y-2" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "font-semibold text-md" }, "Correct Hotspots:"), /* @__PURE__ */ React28__namespace.default.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" }, (correctAnswerIds || []).map((id) => {
1930
2629
  const hotspot = hotspots.find((h) => h.id === id);
1931
- return /* @__PURE__ */ React25__namespace.default.createElement("li", { key: id }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: (hotspot == null ? void 0 : hotspot.description) || (hotspot == null ? void 0 : hotspot.id) || "N/A", className: "inline" }));
2630
+ return /* @__PURE__ */ React28__namespace.default.createElement("li", { key: id }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: (hotspot == null ? void 0 : hotspot.description) || (hotspot == null ? void 0 : hotspot.id) || "N/A", className: "inline" }));
1932
2631
  })))));
1933
2632
  };
1934
2633
  var loadScript = (src, async = true) => {
@@ -2020,10 +2719,10 @@ var loadBlocklyScript = () => {
2020
2719
  });
2021
2720
  };
2022
2721
  var useBlocklyLoader = () => {
2023
- const [isLoading, setIsLoading] = React25.useState(true);
2024
- const [loadError, setLoadError] = React25.useState(null);
2025
- const [isReady, setIsReady] = React25.useState(false);
2026
- const attemptLoad = React25.useCallback(() => {
2722
+ const [isLoading, setIsLoading] = React28.useState(true);
2723
+ const [loadError, setLoadError] = React28.useState(null);
2724
+ const [isReady, setIsReady] = React28.useState(false);
2725
+ const attemptLoad = React28.useCallback(() => {
2027
2726
  setLoadError(null);
2028
2727
  setIsReady(false);
2029
2728
  loadBlocklyScript().then(() => {
@@ -2036,27 +2735,27 @@ var useBlocklyLoader = () => {
2036
2735
  setIsReady(false);
2037
2736
  });
2038
2737
  }, []);
2039
- React25.useEffect(() => {
2738
+ React28.useEffect(() => {
2040
2739
  if (isLoading && !isReady && !loadError) attemptLoad();
2041
2740
  }, [isLoading, isReady, loadError, attemptLoad]);
2042
- const retry = React25.useCallback(() => {
2741
+ const retry = React28.useCallback(() => {
2043
2742
  setLoadError(null);
2044
2743
  setIsReady(false);
2045
2744
  setIsLoading(true);
2046
2745
  }, []);
2047
2746
  return { isLoading, loadError, isReady, retry };
2048
2747
  };
2049
- var BlocklyProgrammingQuestionUI = React25__namespace.default.forwardRef(({
2748
+ var BlocklyProgrammingQuestionUI = React28__namespace.default.forwardRef(({
2050
2749
  question,
2051
2750
  userAnswer,
2052
2751
  showCorrectAnswer = false
2053
2752
  }, ref) => {
2054
- const blocklyDivRef = React25.useRef(null);
2055
- const workspaceRef = React25.useRef(null);
2056
- const [isInitializingComponent, setIsInitializingComponent] = React25.useState(false);
2057
- const [componentError, setComponentError] = React25.useState(null);
2753
+ const blocklyDivRef = React28.useRef(null);
2754
+ const workspaceRef = React28.useRef(null);
2755
+ const [isInitializingComponent, setIsInitializingComponent] = React28.useState(false);
2756
+ const [componentError, setComponentError] = React28.useState(null);
2058
2757
  const { isLoading: blocklyLoading, loadError: blocklyLoadError, isReady: blocklyReady, retry } = useBlocklyLoader();
2059
- React25.useImperativeHandle(ref, () => ({
2758
+ React28.useImperativeHandle(ref, () => ({
2060
2759
  getWorkspaceXml: () => {
2061
2760
  var _a, _b;
2062
2761
  if (workspaceRef.current && blocklyReady) {
@@ -2076,7 +2775,7 @@ var BlocklyProgrammingQuestionUI = React25__namespace.default.forwardRef(({
2076
2775
  return null;
2077
2776
  }
2078
2777
  }));
2079
- const initializeBlocklyWorkspace = React25.useCallback(() => {
2778
+ const initializeBlocklyWorkspace = React28.useCallback(() => {
2080
2779
  var _a;
2081
2780
  if (!blocklyReady || !blocklyDivRef.current) return;
2082
2781
  const LocalBlockly = window.Blockly;
@@ -2184,7 +2883,7 @@ var BlocklyProgrammingQuestionUI = React25__namespace.default.forwardRef(({
2184
2883
  showCorrectAnswer,
2185
2884
  userAnswer
2186
2885
  ]);
2187
- React25.useEffect(() => {
2886
+ React28.useEffect(() => {
2188
2887
  if (blocklyReady && blocklyDivRef.current) {
2189
2888
  initializeBlocklyWorkspace();
2190
2889
  }
@@ -2200,7 +2899,7 @@ var BlocklyProgrammingQuestionUI = React25__namespace.default.forwardRef(({
2200
2899
  }
2201
2900
  };
2202
2901
  }, [blocklyReady, question.id, initializeBlocklyWorkspace]);
2203
- React25.useEffect(() => {
2902
+ React28.useEffect(() => {
2204
2903
  let resizeTimeout;
2205
2904
  const handleResize = () => {
2206
2905
  clearTimeout(resizeTimeout);
@@ -2221,7 +2920,7 @@ var BlocklyProgrammingQuestionUI = React25__namespace.default.forwardRef(({
2221
2920
  }, [blocklyReady]);
2222
2921
  const workspaceHeight = showCorrectAnswer ? "300px" : "450px";
2223
2922
  const workspaceContainerId = `blockly-workspace-container-${question.id}`;
2224
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: question.prompt })), question.points && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", question.points)), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "p-0" }, blocklyLoading && /* @__PURE__ */ React25__namespace.default.createElement(
2923
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: question.prompt })), question.points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", question.points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0" }, blocklyLoading && /* @__PURE__ */ React28__namespace.default.createElement(
2225
2924
  "div",
2226
2925
  {
2227
2926
  style: {
@@ -2233,8 +2932,8 @@ var BlocklyProgrammingQuestionUI = React25__namespace.default.forwardRef(({
2233
2932
  },
2234
2933
  className: "flex items-center justify-center"
2235
2934
  },
2236
- /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "text-center" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-muted-foreground animate-pulse mb-2" }, "Loading Blockly Environment..."), /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "w-8 h-8 border-4 border-muted border-t-primary rounded-full animate-spin mx-auto" }))
2237
- ), blocklyLoadError && !blocklyLoading && /* @__PURE__ */ React25__namespace.default.createElement(
2935
+ /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "text-center" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-muted-foreground animate-pulse mb-2" }, "Loading Blockly Environment..."), /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "w-8 h-8 border-4 border-muted border-t-primary rounded-full animate-spin mx-auto" }))
2936
+ ), blocklyLoadError && !blocklyLoading && /* @__PURE__ */ React28__namespace.default.createElement(
2238
2937
  "div",
2239
2938
  {
2240
2939
  style: {
@@ -2246,14 +2945,14 @@ var BlocklyProgrammingQuestionUI = React25__namespace.default.forwardRef(({
2246
2945
  },
2247
2946
  className: "flex items-center justify-center p-4"
2248
2947
  },
2249
- /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "text-destructive text-center" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "font-semibold text-lg" }, "Failed to load Blockly"), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm mt-2 mb-3" }, blocklyLoadError), /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "space-x-2" }, /* @__PURE__ */ React25__namespace.default.createElement(
2948
+ /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "text-destructive text-center" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "font-semibold text-lg" }, "Failed to load Blockly"), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm mt-2 mb-3" }, blocklyLoadError), /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "space-x-2" }, /* @__PURE__ */ React28__namespace.default.createElement(
2250
2949
  "button",
2251
2950
  {
2252
2951
  onClick: retry,
2253
2952
  className: "px-4 py-2 bg-primary text-primary-foreground rounded hover:bg-primary/90 transition-colors text-sm"
2254
2953
  },
2255
2954
  "Try Again"
2256
- ), /* @__PURE__ */ React25__namespace.default.createElement(
2955
+ ), /* @__PURE__ */ React28__namespace.default.createElement(
2257
2956
  "button",
2258
2957
  {
2259
2958
  onClick: () => window.location.reload(),
@@ -2261,7 +2960,7 @@ var BlocklyProgrammingQuestionUI = React25__namespace.default.forwardRef(({
2261
2960
  },
2262
2961
  "Refresh Page"
2263
2962
  )))
2264
- ), !blocklyLoading && !blocklyLoadError && /* @__PURE__ */ React25__namespace.default.createElement(
2963
+ ), !blocklyLoading && !blocklyLoadError && /* @__PURE__ */ React28__namespace.default.createElement(
2265
2964
  "div",
2266
2965
  {
2267
2966
  id: workspaceContainerId,
@@ -2278,7 +2977,7 @@ var BlocklyProgrammingQuestionUI = React25__namespace.default.forwardRef(({
2278
2977
  },
2279
2978
  "aria-label": `Blockly programming workspace for question: ${question.prompt}`
2280
2979
  }
2281
- ), showCorrectAnswer && question.explanation && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React25__namespace.default.createElement(MarkdownRenderer, { content: question.explanation, className: "text-sm text-accent-foreground/80" }))));
2980
+ ), showCorrectAnswer && question.explanation && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: question.explanation, className: "text-sm text-accent-foreground/80" }))));
2282
2981
  });
2283
2982
  BlocklyProgrammingQuestionUI.displayName = "BlocklyProgrammingQuestionUI";
2284
2983
  var SCRATCH_JS_ENGINE_LAYOUT_PATH = "/static/scratch-blocks/js/blockly_compressed_vertical.js";
@@ -2348,17 +3047,17 @@ var loadScript2 = (src) => {
2348
3047
  loadedScriptPromises.set(fullSrc, promise);
2349
3048
  return promise;
2350
3049
  };
2351
- var ScratchProgrammingQuestionUI = React25.forwardRef(({
3050
+ var ScratchProgrammingQuestionUI = React28.forwardRef(({
2352
3051
  question,
2353
3052
  userAnswer,
2354
3053
  showCorrectAnswer = false
2355
3054
  }, ref) => {
2356
- const blocklyDivRef = React25.useRef(null);
2357
- const workspaceRef = React25.useRef(null);
2358
- const [isBlocklyReady, setIsBlocklyReady] = React25.useState(false);
2359
- const [componentError, setComponentError] = React25.useState(null);
2360
- const [isLoadingScripts, setIsLoadingScripts] = React25.useState(true);
2361
- const attemptLoadScripts = React25.useCallback(async () => {
3055
+ const blocklyDivRef = React28.useRef(null);
3056
+ const workspaceRef = React28.useRef(null);
3057
+ const [isBlocklyReady, setIsBlocklyReady] = React28.useState(false);
3058
+ const [componentError, setComponentError] = React28.useState(null);
3059
+ const [isLoadingScripts, setIsLoadingScripts] = React28.useState(true);
3060
+ const attemptLoadScripts = React28.useCallback(async () => {
2362
3061
  var _a, _b, _c, _d;
2363
3062
  setIsLoadingScripts(true);
2364
3063
  setComponentError(null);
@@ -2410,10 +3109,10 @@ var ScratchProgrammingQuestionUI = React25.forwardRef(({
2410
3109
  setIsLoadingScripts(false);
2411
3110
  }
2412
3111
  }, []);
2413
- React25.useEffect(() => {
3112
+ React28.useEffect(() => {
2414
3113
  attemptLoadScripts();
2415
3114
  }, [attemptLoadScripts]);
2416
- React25.useImperativeHandle(ref, () => ({
3115
+ React28.useImperativeHandle(ref, () => ({
2417
3116
  getWorkspaceXml: () => {
2418
3117
  const LocalBlockly = window.Blockly;
2419
3118
  if (workspaceRef.current && (LocalBlockly == null ? void 0 : LocalBlockly.Xml)) {
@@ -2428,7 +3127,7 @@ var ScratchProgrammingQuestionUI = React25.forwardRef(({
2428
3127
  return null;
2429
3128
  }
2430
3129
  }));
2431
- const initializeWorkspace = React25.useCallback(() => {
3130
+ const initializeWorkspace = React28.useCallback(() => {
2432
3131
  var _a, _b;
2433
3132
  const LocalBlockly = window.Blockly;
2434
3133
  if (!isBlocklyReady || !blocklyDivRef.current || !LocalBlockly) {
@@ -2532,7 +3231,7 @@ var ScratchProgrammingQuestionUI = React25.forwardRef(({
2532
3231
  setComponentError(`ScratchUI: Workspace initialization failed: ${e.message || String(e)}. Check console for details. Toolbox used: ${question.toolboxDefinition ? "Custom" : "Default Simplified"}.`);
2533
3232
  }
2534
3233
  }, [question, showCorrectAnswer, userAnswer, isBlocklyReady]);
2535
- React25.useEffect(() => {
3234
+ React28.useEffect(() => {
2536
3235
  if (isBlocklyReady) {
2537
3236
  initializeWorkspace();
2538
3237
  }
@@ -2559,15 +3258,15 @@ var ScratchProgrammingQuestionUI = React25.forwardRef(({
2559
3258
  }, [isBlocklyReady, initializeWorkspace]);
2560
3259
  const workspaceHeight = showCorrectAnswer ? "300px" : "450px";
2561
3260
  if (isLoadingScripts) {
2562
- return /* @__PURE__ */ React25__namespace.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__namespace.default.createElement("div", { className: "text-center" }, /* @__PURE__ */ React25__namespace.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__namespace.default.createElement("p", { className: "text-muted-foreground" }, "Loading Scratch Assets...")));
3261
+ return /* @__PURE__ */ React28__namespace.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__namespace.default.createElement("div", { className: "text-center" }, /* @__PURE__ */ React28__namespace.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__namespace.default.createElement("p", { className: "text-muted-foreground" }, "Loading Scratch Assets...")));
2563
3262
  }
2564
3263
  if (componentError) {
2565
- return /* @__PURE__ */ React25__namespace.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__namespace.default.createElement("p", { className: "font-semibold text-lg mb-2" }, "Failed to load Scratch Workspace."), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm text-muted-foreground mb-3 text-center" }, componentError), /* @__PURE__ */ React25__namespace.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__namespace.default.createElement("code", null, "public/static/scratch-blocks/js"), " directory. Check browser console for more details.", /* @__PURE__ */ React25__namespace.default.createElement("br", null), /* @__PURE__ */ React25__namespace.default.createElement("strong", null, "CRITICAL: Ensure you have copied the CSS files from ", /* @__PURE__ */ React25__namespace.default.createElement("code", null, "node_modules/scratch-blocks/css/"), " (e.g., ", /* @__PURE__ */ React25__namespace.default.createElement("code", null, "vertical.css"), ") to ", /* @__PURE__ */ React25__namespace.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__namespace.default.createElement(Button, { onClick: attemptLoadScripts, variant: "outline" }, "Try Reloading Scripts"));
3264
+ return /* @__PURE__ */ React28__namespace.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__namespace.default.createElement("p", { className: "font-semibold text-lg mb-2" }, "Failed to load Scratch Workspace."), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm text-muted-foreground mb-3 text-center" }, componentError), /* @__PURE__ */ React28__namespace.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__namespace.default.createElement("code", null, "public/static/scratch-blocks/js"), " directory. Check browser console for more details.", /* @__PURE__ */ React28__namespace.default.createElement("br", null), /* @__PURE__ */ React28__namespace.default.createElement("strong", null, "CRITICAL: Ensure you have copied the CSS files from ", /* @__PURE__ */ React28__namespace.default.createElement("code", null, "node_modules/scratch-blocks/css/"), " (e.g., ", /* @__PURE__ */ React28__namespace.default.createElement("code", null, "vertical.css"), ") to ", /* @__PURE__ */ React28__namespace.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__namespace.default.createElement(Button, { onClick: attemptLoadScripts, variant: "outline" }, "Try Reloading Scripts"));
2566
3265
  }
2567
3266
  if (!isBlocklyReady && !isLoadingScripts) {
2568
- return /* @__PURE__ */ React25__namespace.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__namespace.default.createElement("p", { className: "text-muted-foreground" }, "Scratch environment did not initialize (Blockly not ready). Check console for script loading errors."));
3267
+ return /* @__PURE__ */ React28__namespace.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__namespace.default.createElement("p", { className: "text-muted-foreground" }, "Scratch environment did not initialize (Blockly not ready). Check console for script loading errors."));
2569
3268
  }
2570
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, question.prompt), question.points && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", question.points)), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React25__namespace.default.createElement(
3269
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, question.prompt), question.points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", question.points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0" }, /* @__PURE__ */ React28__namespace.default.createElement(
2571
3270
  "div",
2572
3271
  {
2573
3272
  ref: blocklyDivRef,
@@ -2583,17 +3282,237 @@ var ScratchProgrammingQuestionUI = React25.forwardRef(({
2583
3282
  },
2584
3283
  "aria-label": `Scratch programming workspace for question: ${question.prompt}`
2585
3284
  }
2586
- ), showCorrectAnswer && question.explanation && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm text-accent-foreground/80" }, question.explanation))));
3285
+ ), showCorrectAnswer && question.explanation && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm text-accent-foreground/80" }, question.explanation))));
2587
3286
  });
2588
3287
  ScratchProgrammingQuestionUI.displayName = "ScratchProgrammingQuestionUI";
3288
+ var Tabs = TabsPrimitive__namespace.Root;
3289
+ var TabsList = React28__namespace.forwardRef((_a, ref) => {
3290
+ var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
3291
+ return /* @__PURE__ */ React28__namespace.createElement(
3292
+ TabsPrimitive__namespace.List,
3293
+ __spreadValues({
3294
+ ref,
3295
+ className: cn(
3296
+ "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
3297
+ className
3298
+ )
3299
+ }, props)
3300
+ );
3301
+ });
3302
+ TabsList.displayName = TabsPrimitive__namespace.List.displayName;
3303
+ var TabsTrigger = React28__namespace.forwardRef((_a, ref) => {
3304
+ var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
3305
+ return /* @__PURE__ */ React28__namespace.createElement(
3306
+ TabsPrimitive__namespace.Trigger,
3307
+ __spreadValues({
3308
+ ref,
3309
+ className: cn(
3310
+ "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",
3311
+ className
3312
+ )
3313
+ }, props)
3314
+ );
3315
+ });
3316
+ TabsTrigger.displayName = TabsPrimitive__namespace.Trigger.displayName;
3317
+ var TabsContent = React28__namespace.forwardRef((_a, ref) => {
3318
+ var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
3319
+ return /* @__PURE__ */ React28__namespace.createElement(
3320
+ TabsPrimitive__namespace.Content,
3321
+ __spreadValues({
3322
+ ref,
3323
+ className: cn(
3324
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
3325
+ className
3326
+ )
3327
+ }, props)
3328
+ );
3329
+ });
3330
+ TabsContent.displayName = TabsPrimitive__namespace.Content.displayName;
3331
+ var TOAST_LIMIT = 1;
3332
+ var TOAST_REMOVE_DELAY = 1e6;
3333
+ var count = 0;
3334
+ function genId() {
3335
+ count = (count + 1) % Number.MAX_SAFE_INTEGER;
3336
+ return count.toString();
3337
+ }
3338
+ var toastTimeouts = /* @__PURE__ */ new Map();
3339
+ var addToRemoveQueue = (toastId) => {
3340
+ if (toastTimeouts.has(toastId)) {
3341
+ return;
3342
+ }
3343
+ const timeout = setTimeout(() => {
3344
+ toastTimeouts.delete(toastId);
3345
+ dispatch({
3346
+ type: "REMOVE_TOAST",
3347
+ toastId
3348
+ });
3349
+ }, TOAST_REMOVE_DELAY);
3350
+ toastTimeouts.set(toastId, timeout);
3351
+ };
3352
+ var reducer = (state, action) => {
3353
+ switch (action.type) {
3354
+ case "ADD_TOAST":
3355
+ return __spreadProps(__spreadValues({}, state), {
3356
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT)
3357
+ });
3358
+ case "UPDATE_TOAST":
3359
+ return __spreadProps(__spreadValues({}, state), {
3360
+ toasts: state.toasts.map(
3361
+ (t) => t.id === action.toast.id ? __spreadValues(__spreadValues({}, t), action.toast) : t
3362
+ )
3363
+ });
3364
+ case "DISMISS_TOAST": {
3365
+ const { toastId } = action;
3366
+ if (toastId) {
3367
+ addToRemoveQueue(toastId);
3368
+ } else {
3369
+ state.toasts.forEach((toast2) => {
3370
+ addToRemoveQueue(toast2.id);
3371
+ });
3372
+ }
3373
+ return __spreadProps(__spreadValues({}, state), {
3374
+ toasts: state.toasts.map(
3375
+ (t) => t.id === toastId || toastId === void 0 ? __spreadProps(__spreadValues({}, t), {
3376
+ open: false
3377
+ }) : t
3378
+ )
3379
+ });
3380
+ }
3381
+ case "REMOVE_TOAST":
3382
+ if (action.toastId === void 0) {
3383
+ return __spreadProps(__spreadValues({}, state), {
3384
+ toasts: []
3385
+ });
3386
+ }
3387
+ return __spreadProps(__spreadValues({}, state), {
3388
+ toasts: state.toasts.filter((t) => t.id !== action.toastId)
3389
+ });
3390
+ }
3391
+ };
3392
+ var listeners = [];
3393
+ var memoryState = { toasts: [] };
3394
+ function dispatch(action) {
3395
+ memoryState = reducer(memoryState, action);
3396
+ listeners.forEach((listener) => {
3397
+ listener(memoryState);
3398
+ });
3399
+ }
3400
+ function toast(_a) {
3401
+ var props = __objRest(_a, []);
3402
+ const id = genId();
3403
+ const update = (props2) => dispatch({
3404
+ type: "UPDATE_TOAST",
3405
+ toast: __spreadProps(__spreadValues({}, props2), { id })
3406
+ });
3407
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
3408
+ dispatch({
3409
+ type: "ADD_TOAST",
3410
+ toast: __spreadProps(__spreadValues({}, props), {
3411
+ id,
3412
+ open: true,
3413
+ onOpenChange: (open) => {
3414
+ if (!open) dismiss();
3415
+ }
3416
+ })
3417
+ });
3418
+ return {
3419
+ id,
3420
+ dismiss,
3421
+ update
3422
+ };
3423
+ }
3424
+ function useToast() {
3425
+ const [state, setState] = React28__namespace.useState(memoryState);
3426
+ React28__namespace.useEffect(() => {
3427
+ listeners.push(setState);
3428
+ return () => {
3429
+ const index = listeners.indexOf(setState);
3430
+ if (index > -1) {
3431
+ listeners.splice(index, 1);
3432
+ }
3433
+ };
3434
+ }, [state]);
3435
+ return __spreadProps(__spreadValues({}, state), {
3436
+ toast,
3437
+ dismiss: (toastId) => dispatch({ type: "DISMISS_TOAST", toastId })
3438
+ });
3439
+ }
2589
3440
 
2590
- // src/react-ui/components/ui/QuestionRenderer.tsx
2591
- var QuestionRenderer = React25__namespace.default.forwardRef(({
3441
+ // src/react-ui/components/ui/CodingQuestionUI.tsx
3442
+ var languageMap = {
3443
+ cpp: langCpp.cpp(),
3444
+ javascript: langJavascript.javascript({ typescript: true }),
3445
+ // Use TS extension for better JS support
3446
+ python: langPython.python(),
3447
+ swift: langJavascript.javascript({ typescript: true }),
3448
+ // Fallback for Swift syntax highlighting
3449
+ csharp: langCpp.cpp()
3450
+ // Fallback for C# syntax highlighting
3451
+ };
3452
+ var CodingQuestionUI = ({
3453
+ question,
3454
+ onAnswerChange,
3455
+ userAnswer,
3456
+ showCorrectAnswer = false
3457
+ }) => {
3458
+ const [code, setCode] = React28.useState("");
3459
+ const [isRunningTests, setIsRunningTests] = React28.useState(false);
3460
+ const [testResults, setTestResults] = React28.useState([]);
3461
+ const { toast: toast2 } = useToast();
3462
+ React28.useEffect(() => {
3463
+ const initialCode = typeof userAnswer === "string" ? userAnswer : question.functionSignature || "";
3464
+ setCode(initialCode);
3465
+ }, [question.id, userAnswer, question.functionSignature]);
3466
+ const handleCodeChange = React28.useCallback((value) => {
3467
+ setCode(value);
3468
+ onAnswerChange(value);
3469
+ }, [onAnswerChange]);
3470
+ const handleRunPublicTests = async () => {
3471
+ setIsRunningTests(true);
3472
+ setTestResults([]);
3473
+ try {
3474
+ const evaluationService = new CodeEvaluationService();
3475
+ const results = await evaluationService.evaluatePublicTestCases(question, code);
3476
+ setTestResults(results);
3477
+ } catch (error) {
3478
+ toast2({
3479
+ title: "Evaluation Error",
3480
+ description: error instanceof Error ? error.message : "An unknown error occurred.",
3481
+ variant: "destructive"
3482
+ });
3483
+ } finally {
3484
+ setIsRunningTests(false);
3485
+ }
3486
+ };
3487
+ const langExtension = languageMap[question.codingLanguage];
3488
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full border-none shadow-none" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, { className: "p-0 pb-4" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl mb-1 font-body" }, /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: question.prompt })), question.points && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-sm text-muted-foreground" }, "Points: ", question.points)), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "p-0 space-y-4" }, /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "font-mono text-sm border rounded-md overflow-hidden" }, /* @__PURE__ */ React28__namespace.default.createElement(
3489
+ CodeMirror__default.default,
3490
+ {
3491
+ value: code,
3492
+ height: "300px",
3493
+ extensions: [langExtension],
3494
+ onChange: handleCodeChange,
3495
+ readOnly: showCorrectAnswer,
3496
+ theme: "dark"
3497
+ }
3498
+ )), !showCorrectAnswer && /* @__PURE__ */ React28__namespace.default.createElement(Button, { onClick: handleRunPublicTests, disabled: isRunningTests }, isRunningTests ? /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.Play, { className: "mr-2 h-4 w-4" }), isRunningTests ? "Running..." : "Run Public Tests"), /* @__PURE__ */ React28__namespace.default.createElement(Tabs, { defaultValue: "tests", className: "w-full" }, /* @__PURE__ */ React28__namespace.default.createElement(TabsList, null, /* @__PURE__ */ React28__namespace.default.createElement(TabsTrigger, { value: "tests" }, "Test Cases"), showCorrectAnswer && /* @__PURE__ */ React28__namespace.default.createElement(TabsTrigger, { value: "solution" }, "Solution")), /* @__PURE__ */ React28__namespace.default.createElement(TabsContent, { value: "tests" }, /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "space-y-2 p-2 border rounded-md min-h-[100px]" }, testResults.length > 0 ? testResults.map((result, index) => /* @__PURE__ */ React28__namespace.default.createElement("div", { key: result.testCaseId, className: "flex items-center text-sm" }, result.passed ? /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.CheckCircle, { className: "h-4 w-4 text-green-500 mr-2" }) : /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.XCircle, { className: "h-4 w-4 text-destructive mr-2" }), /* @__PURE__ */ React28__namespace.default.createElement("span", null, "Test Case #", index + 1, ": ", result.passed ? "Passed" : "Failed"), !result.passed && /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-xs text-muted-foreground ml-2" }, "- ", result.reasoning))) : /* @__PURE__ */ React28__namespace.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__namespace.default.createElement(TabsContent, { value: "solution" }, /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "font-mono text-sm border rounded-md overflow-hidden" }, /* @__PURE__ */ React28__namespace.default.createElement(
3499
+ CodeMirror__default.default,
3500
+ {
3501
+ value: question.solutionCode,
3502
+ height: "300px",
3503
+ extensions: [langExtension],
3504
+ readOnly: true,
3505
+ theme: "dark"
3506
+ }
3507
+ )))), showCorrectAnswer && question.explanation && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-4 p-3 bg-accent/20 border border-accent rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm font-semibold text-accent-foreground" }, "Explanation:"), /* @__PURE__ */ React28__namespace.default.createElement(MarkdownRenderer, { content: question.explanation, className: "text-sm text-accent-foreground/80" }))));
3508
+ };
3509
+ var QuestionRenderer = React28__namespace.default.forwardRef(({
2592
3510
  question,
2593
3511
  onAnswerChange,
2594
3512
  userAnswer,
2595
3513
  showCorrectAnswer = false
2596
3514
  }, ref) => {
3515
+ const { t } = reactI18next.useTranslation();
2597
3516
  const commonProps = {
2598
3517
  question,
2599
3518
  onAnswerChange,
@@ -2602,37 +3521,39 @@ var QuestionRenderer = React25__namespace.default.forwardRef(({
2602
3521
  };
2603
3522
  switch (question.questionType) {
2604
3523
  case "multiple_choice":
2605
- return /* @__PURE__ */ React25__namespace.default.createElement(MultipleChoiceQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3524
+ return /* @__PURE__ */ React28__namespace.default.createElement(MultipleChoiceQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2606
3525
  case "true_false":
2607
- return /* @__PURE__ */ React25__namespace.default.createElement(TrueFalseQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3526
+ return /* @__PURE__ */ React28__namespace.default.createElement(TrueFalseQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2608
3527
  case "multiple_response":
2609
- return /* @__PURE__ */ React25__namespace.default.createElement(MultipleResponseQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3528
+ return /* @__PURE__ */ React28__namespace.default.createElement(MultipleResponseQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2610
3529
  case "short_answer":
2611
- return /* @__PURE__ */ React25__namespace.default.createElement(ShortAnswerQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3530
+ return /* @__PURE__ */ React28__namespace.default.createElement(ShortAnswerQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2612
3531
  case "numeric":
2613
- return /* @__PURE__ */ React25__namespace.default.createElement(NumericQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3532
+ return /* @__PURE__ */ React28__namespace.default.createElement(NumericQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2614
3533
  case "fill_in_the_blanks":
2615
- return /* @__PURE__ */ React25__namespace.default.createElement(FillInTheBlanksQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3534
+ return /* @__PURE__ */ React28__namespace.default.createElement(FillInTheBlanksQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2616
3535
  case "sequence":
2617
- return /* @__PURE__ */ React25__namespace.default.createElement(SequenceQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3536
+ return /* @__PURE__ */ React28__namespace.default.createElement(SequenceQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2618
3537
  case "matching":
2619
- return /* @__PURE__ */ React25__namespace.default.createElement(MatchingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3538
+ return /* @__PURE__ */ React28__namespace.default.createElement(MatchingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2620
3539
  case "drag_and_drop":
2621
- return /* @__PURE__ */ React25__namespace.default.createElement(DragAndDropQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3540
+ return /* @__PURE__ */ React28__namespace.default.createElement(DragAndDropQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2622
3541
  case "hotspot":
2623
- return /* @__PURE__ */ React25__namespace.default.createElement(HotspotQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
3542
+ return /* @__PURE__ */ React28__namespace.default.createElement(HotspotQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2624
3543
  case "blockly_programming":
2625
- return /* @__PURE__ */ React25__namespace.default.createElement(BlocklyProgrammingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question, ref }));
3544
+ return /* @__PURE__ */ React28__namespace.default.createElement(BlocklyProgrammingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question, ref }));
2626
3545
  case "scratch_programming":
2627
- return /* @__PURE__ */ React25__namespace.default.createElement(ScratchProgrammingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question, ref }));
3546
+ return /* @__PURE__ */ React28__namespace.default.createElement(ScratchProgrammingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question, ref }));
3547
+ case "coding":
3548
+ return /* @__PURE__ */ React28__namespace.default.createElement(CodingQuestionUI, __spreadProps(__spreadValues({}, commonProps), { question }));
2628
3549
  default:
2629
- return /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "p-4 border border-destructive bg-destructive/10 rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "font-semibold text-destructive" }, "Unsupported Question Type"), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, "The question type is not currently supported by the renderer."), /* @__PURE__ */ React25__namespace.default.createElement("pre", { className: "mt-2 p-2 bg-muted rounded text-xs font-code overflow-x-auto" }, JSON.stringify(question, null, 2)));
3550
+ return /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "p-4 border border-destructive bg-destructive/10 rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "font-semibold text-destructive" }, t("unsupportedQuestionType")), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, t("unsupportedQuestionTypeDescription")), /* @__PURE__ */ React28__namespace.default.createElement("pre", { className: "mt-2 p-2 bg-muted rounded text-xs font-code overflow-x-auto" }, JSON.stringify(question, null, 2)));
2630
3551
  }
2631
3552
  });
2632
3553
  QuestionRenderer.displayName = "QuestionRenderer";
2633
- var Progress = React25__namespace.forwardRef((_a, ref) => {
3554
+ var Progress = React28__namespace.forwardRef((_a, ref) => {
2634
3555
  var _b = _a, { className, value } = _b, props = __objRest(_b, ["className", "value"]);
2635
- return /* @__PURE__ */ React25__namespace.createElement(
3556
+ return /* @__PURE__ */ React28__namespace.createElement(
2636
3557
  ProgressPrimitive__namespace.Root,
2637
3558
  __spreadValues({
2638
3559
  ref,
@@ -2641,7 +3562,7 @@ var Progress = React25__namespace.forwardRef((_a, ref) => {
2641
3562
  className
2642
3563
  )
2643
3564
  }, props),
2644
- /* @__PURE__ */ React25__namespace.createElement(
3565
+ /* @__PURE__ */ React28__namespace.createElement(
2645
3566
  ProgressPrimitive__namespace.Indicator,
2646
3567
  {
2647
3568
  className: "h-full w-full flex-1 bg-primary transition-all",
@@ -2652,9 +3573,9 @@ var Progress = React25__namespace.forwardRef((_a, ref) => {
2652
3573
  });
2653
3574
  Progress.displayName = ProgressPrimitive__namespace.Root.displayName;
2654
3575
  var Accordion = AccordionPrimitive__namespace.Root;
2655
- var AccordionItem = React25__namespace.forwardRef((_a, ref) => {
3576
+ var AccordionItem = React28__namespace.forwardRef((_a, ref) => {
2656
3577
  var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
2657
- return /* @__PURE__ */ React25__namespace.createElement(
3578
+ return /* @__PURE__ */ React28__namespace.createElement(
2658
3579
  AccordionPrimitive__namespace.Item,
2659
3580
  __spreadValues({
2660
3581
  ref,
@@ -2663,9 +3584,9 @@ var AccordionItem = React25__namespace.forwardRef((_a, ref) => {
2663
3584
  );
2664
3585
  });
2665
3586
  AccordionItem.displayName = "AccordionItem";
2666
- var AccordionTrigger = React25__namespace.forwardRef((_a, ref) => {
3587
+ var AccordionTrigger = React28__namespace.forwardRef((_a, ref) => {
2667
3588
  var _b = _a, { className, children } = _b, props = __objRest(_b, ["className", "children"]);
2668
- return /* @__PURE__ */ React25__namespace.createElement(AccordionPrimitive__namespace.Header, { className: "flex" }, /* @__PURE__ */ React25__namespace.createElement(
3589
+ return /* @__PURE__ */ React28__namespace.createElement(AccordionPrimitive__namespace.Header, { className: "flex" }, /* @__PURE__ */ React28__namespace.createElement(
2669
3590
  AccordionPrimitive__namespace.Trigger,
2670
3591
  __spreadValues({
2671
3592
  ref,
@@ -2675,39 +3596,39 @@ var AccordionTrigger = React25__namespace.forwardRef((_a, ref) => {
2675
3596
  )
2676
3597
  }, props),
2677
3598
  children,
2678
- /* @__PURE__ */ React25__namespace.createElement(lucideReact.ChevronDown, { className: "h-4 w-4 shrink-0 transition-transform duration-200" })
3599
+ /* @__PURE__ */ React28__namespace.createElement(lucideReact.ChevronDown, { className: "h-4 w-4 shrink-0 transition-transform duration-200" })
2679
3600
  ));
2680
3601
  });
2681
3602
  AccordionTrigger.displayName = AccordionPrimitive__namespace.Trigger.displayName;
2682
- var AccordionContent = React25__namespace.forwardRef((_a, ref) => {
3603
+ var AccordionContent = React28__namespace.forwardRef((_a, ref) => {
2683
3604
  var _b = _a, { className, children } = _b, props = __objRest(_b, ["className", "children"]);
2684
- return /* @__PURE__ */ React25__namespace.createElement(
3605
+ return /* @__PURE__ */ React28__namespace.createElement(
2685
3606
  AccordionPrimitive__namespace.Content,
2686
3607
  __spreadValues({
2687
3608
  ref,
2688
3609
  className: "overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
2689
3610
  }, props),
2690
- /* @__PURE__ */ React25__namespace.createElement("div", { className: cn("pb-4 pt-0", className) }, children)
3611
+ /* @__PURE__ */ React28__namespace.createElement("div", { className: cn("pb-4 pt-0", className) }, children)
2691
3612
  );
2692
3613
  });
2693
3614
  AccordionContent.displayName = AccordionPrimitive__namespace.Content.displayName;
2694
- var ScrollArea = React25__namespace.forwardRef((_a, ref) => {
3615
+ var ScrollArea = React28__namespace.forwardRef((_a, ref) => {
2695
3616
  var _b = _a, { className, children } = _b, props = __objRest(_b, ["className", "children"]);
2696
- return /* @__PURE__ */ React25__namespace.createElement(
3617
+ return /* @__PURE__ */ React28__namespace.createElement(
2697
3618
  ScrollAreaPrimitive__namespace.Root,
2698
3619
  __spreadValues({
2699
3620
  ref,
2700
3621
  className: cn("relative overflow-hidden", className)
2701
3622
  }, props),
2702
- /* @__PURE__ */ React25__namespace.createElement(ScrollAreaPrimitive__namespace.Viewport, { className: "h-full w-full rounded-[inherit]" }, children),
2703
- /* @__PURE__ */ React25__namespace.createElement(ScrollBar, null),
2704
- /* @__PURE__ */ React25__namespace.createElement(ScrollAreaPrimitive__namespace.Corner, null)
3623
+ /* @__PURE__ */ React28__namespace.createElement(ScrollAreaPrimitive__namespace.Viewport, { className: "h-full w-full rounded-[inherit]" }, children),
3624
+ /* @__PURE__ */ React28__namespace.createElement(ScrollBar, null),
3625
+ /* @__PURE__ */ React28__namespace.createElement(ScrollAreaPrimitive__namespace.Corner, null)
2705
3626
  );
2706
3627
  });
2707
3628
  ScrollArea.displayName = ScrollAreaPrimitive__namespace.Root.displayName;
2708
- var ScrollBar = React25__namespace.forwardRef((_a, ref) => {
3629
+ var ScrollBar = React28__namespace.forwardRef((_a, ref) => {
2709
3630
  var _b = _a, { className, orientation = "vertical" } = _b, props = __objRest(_b, ["className", "orientation"]);
2710
- return /* @__PURE__ */ React25__namespace.createElement(
3631
+ return /* @__PURE__ */ React28__namespace.createElement(
2711
3632
  ScrollAreaPrimitive__namespace.ScrollAreaScrollbar,
2712
3633
  __spreadValues({
2713
3634
  ref,
@@ -2719,83 +3640,126 @@ var ScrollBar = React25__namespace.forwardRef((_a, ref) => {
2719
3640
  className
2720
3641
  )
2721
3642
  }, props),
2722
- /* @__PURE__ */ React25__namespace.createElement(ScrollAreaPrimitive__namespace.ScrollAreaThumb, { className: "relative flex-1 rounded-full bg-border" })
3643
+ /* @__PURE__ */ React28__namespace.createElement(ScrollAreaPrimitive__namespace.ScrollAreaThumb, { className: "relative flex-1 rounded-full bg-border" })
2723
3644
  );
2724
3645
  });
2725
3646
  ScrollBar.displayName = ScrollAreaPrimitive__namespace.ScrollAreaScrollbar.displayName;
2726
3647
 
2727
3648
  // src/react-ui/components/ui/QuizResult.tsx
2728
- var QuizResult = ({ result, quizTitle, onExitQuiz }) => {
3649
+ var QuizResult = ({
3650
+ result,
3651
+ quizTitle,
3652
+ onExitQuiz,
3653
+ onGenerateReview,
3654
+ showReviewButton = false,
3655
+ isReviewLoading = false
3656
+ }) => {
2729
3657
  var _a, _b, _c, _d;
3658
+ const { t } = reactI18next.useTranslation();
2730
3659
  const getAnswerDisplay = (answer) => {
2731
- if (answer === null || answer === void 0) return "Not Answered";
3660
+ if (answer === null || answer === void 0) return t("practiceFlow.results.notAnswered");
2732
3661
  if (typeof answer === "boolean") return answer ? "True" : "False";
2733
3662
  if (Array.isArray(answer)) return answer.join(", ");
2734
- if (typeof answer === "object") return JSON.stringify(answer);
3663
+ if (typeof answer === "object") {
3664
+ if (answer.hasOwnProperty("value")) {
3665
+ const value = answer.value;
3666
+ if (value === null || value === void 0) return t("practiceFlow.results.notAnswered");
3667
+ if (typeof value === "boolean") return value ? "True" : "False";
3668
+ if (Array.isArray(value)) return value.join(", ");
3669
+ if (typeof value === "object") return JSON.stringify(value, null, 2);
3670
+ return String(value);
3671
+ }
3672
+ return JSON.stringify(answer, null, 2);
3673
+ }
2735
3674
  return String(answer);
2736
3675
  };
2737
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full max-w-3xl mx-auto shadow-xl" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-3xl font-headline text-center" }, "Quiz Results: ", quizTitle), /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, { className: "text-center text-lg" }, "Here's how you performed!")), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "space-y-6" }, /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "bg-secondary/50" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-xl flex items-center" }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.BarChart2, { className: "mr-2 h-5 w-5 text-primary" }), "Overall Score")), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "grid grid-cols-1 md:grid-cols-3 gap-4 text-center" }, /* @__PURE__ */ React25__namespace.default.createElement("div", null, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.score, " / ", result.maxScore), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, "Points")), /* @__PURE__ */ React25__namespace.default.createElement("div", null, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.percentage.toFixed(2), "%"), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, "Percentage")), /* @__PURE__ */ React25__namespace.default.createElement("div", null, result.passed !== void 0 && (result.passed ? /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex flex-col items-center text-green-600" }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.CheckCircle, { className: "h-10 w-10" }), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-xl font-semibold mt-1" }, "Passed")) : /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex flex-col items-center text-destructive" }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.XCircle, { className: "h-10 w-10" }), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-xl font-semibold mt-1" }, "Failed")))))), /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm" }, /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.Clock, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React25__namespace.default.createElement("span", null, "Total time spent:"), /* @__PURE__ */ React25__namespace.default.createElement("span", { className: "font-semibold" }, (_b = (_a = result.totalTimeSpentSeconds) == null ? void 0 : _a.toFixed(0)) != null ? _b : "N/A", " seconds")), /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.Percent, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React25__namespace.default.createElement("span", null, "Average time per question:"), /* @__PURE__ */ React25__namespace.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__namespace.default.createElement(Card, null, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-lg" }, "SCORM Sync Status")), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: `flex items-center ${result.scormStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.scormStatus === "error" && /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.AlertTriangle, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React25__namespace.default.createElement("span", { className: "font-semibold ml-1" }, result.scormStatus)), result.scormError && /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.scormError))), result.webhookStatus && result.webhookStatus !== "idle" && /* @__PURE__ */ React25__namespace.default.createElement(Card, null, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-lg" }, "Webhook Sync Status")), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React25__namespace.default.createElement("p", { className: `flex items-center ${result.webhookStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.webhookStatus === "error" && /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.AlertTriangle, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React25__namespace.default.createElement("span", { className: "font-semibold ml-1" }, result.webhookStatus)), result.webhookError && /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.webhookError))), /* @__PURE__ */ React25__namespace.default.createElement(Accordion, { type: "single", collapsible: true, className: "w-full" }, /* @__PURE__ */ React25__namespace.default.createElement(AccordionItem, { value: "question-breakdown" }, /* @__PURE__ */ React25__namespace.default.createElement(AccordionTrigger, { className: "text-lg font-semibold" }, "Detailed Question Breakdown"), /* @__PURE__ */ React25__namespace.default.createElement(AccordionContent, null, /* @__PURE__ */ React25__namespace.default.createElement(ScrollArea, { className: "h-[300px] pr-4" }, /* @__PURE__ */ React25__namespace.default.createElement("ul", { className: "space-y-4" }, result.questionResults.map((qResult, index) => {
3676
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full max-w-3xl mx-auto shadow-xl" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-3xl font-headline text-center" }, t("practiceFlow.results.title", { quizTitle })), /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, { className: "text-center text-lg" }, t("practiceFlow.results.description"))), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "space-y-6" }, /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "bg-secondary/50" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-xl flex items-center" }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.BarChart2, { className: "mr-2 h-5 w-5 text-primary" }), t("practiceFlow.results.overallScore"))), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "grid grid-cols-1 md:grid-cols-3 gap-4 text-center" }, /* @__PURE__ */ React28__namespace.default.createElement("div", null, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.score, " / ", result.maxScore), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, t("practiceFlow.results.points"))), /* @__PURE__ */ React28__namespace.default.createElement("div", null, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.percentage.toFixed(2), "%"), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, t("practiceFlow.results.percentage"))), /* @__PURE__ */ React28__namespace.default.createElement("div", null, result.passed !== void 0 && (result.passed ? /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "flex flex-col items-center text-green-600" }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.CheckCircle, { className: "h-10 w-10" }), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-xl font-semibold mt-1" }, t("practiceFlow.results.passed"))) : /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "flex flex-col items-center text-destructive" }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.XCircle, { className: "h-10 w-10" }), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-xl font-semibold mt-1" }, t("practiceFlow.results.failed"))))))), /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm" }, /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.Clock, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React28__namespace.default.createElement("span", null, t("practiceFlow.results.timeSpent")), /* @__PURE__ */ React28__namespace.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__namespace.default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.Percent, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React28__namespace.default.createElement("span", null, t("practiceFlow.results.avgTimePerQuestion")), /* @__PURE__ */ React28__namespace.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__namespace.default.createElement(Card, null, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-lg" }, "SCORM Sync Status")), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: `flex items-center ${result.scormStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.scormStatus === "error" && /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.AlertTriangle, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React28__namespace.default.createElement("span", { className: "font-semibold ml-1" }, result.scormStatus)), result.scormError && /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.scormError))), result.webhookStatus && result.webhookStatus !== "idle" && /* @__PURE__ */ React28__namespace.default.createElement(Card, null, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-lg" }, "Webhook Sync Status")), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React28__namespace.default.createElement("p", { className: `flex items-center ${result.webhookStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.webhookStatus === "error" && /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.AlertTriangle, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React28__namespace.default.createElement("span", { className: "font-semibold ml-1" }, result.webhookStatus)), result.webhookError && /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.webhookError))), /* @__PURE__ */ React28__namespace.default.createElement(Accordion, { type: "single", collapsible: true, className: "w-full" }, /* @__PURE__ */ React28__namespace.default.createElement(AccordionItem, { value: "question-breakdown" }, /* @__PURE__ */ React28__namespace.default.createElement(AccordionTrigger, { className: "text-lg font-semibold" }, t("practiceFlow.results.questionBreakdown")), /* @__PURE__ */ React28__namespace.default.createElement(AccordionContent, null, /* @__PURE__ */ React28__namespace.default.createElement(ScrollArea, { className: "h-[300px] pr-4" }, /* @__PURE__ */ React28__namespace.default.createElement("ul", { className: "space-y-4" }, result.questionResults.map((qResult, index) => {
2738
3677
  var _a2, _b2;
2739
- return /* @__PURE__ */ React25__namespace.default.createElement("li", { key: qResult.questionId, className: "p-4 border rounded-md bg-background" }, /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React25__namespace.default.createElement("h4", { className: "font-semibold" }, "Question ", index + 1), qResult.isCorrect ? /* @__PURE__ */ React25__namespace.default.createElement("span", { className: "text-green-600 font-medium flex items-center" }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.CheckCircle, { className: "mr-1 h-4 w-4" }), " Correct") : /* @__PURE__ */ React25__namespace.default.createElement("span", { className: "text-destructive font-medium flex items-center" }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.XCircle, { className: "mr-1 h-4 w-4" }), " Incorrect")), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React25__namespace.default.createElement("span", { className: "font-medium" }, "Your Answer:"), " ", getAnswerDisplay(qResult.userAnswer)), !qResult.isCorrect && /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React25__namespace.default.createElement("span", { className: "font-medium" }, "Correct Answer:"), " ", getAnswerDisplay(qResult.correctAnswer)), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, /* @__PURE__ */ React25__namespace.default.createElement("span", { className: "font-medium" }, "Points Earned:"), " ", qResult.pointsEarned), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, /* @__PURE__ */ React25__namespace.default.createElement("span", { className: "font-medium" }, "Time Spent:"), " ", (_b2 = (_a2 = qResult.timeSpentSeconds) == null ? void 0 : _a2.toFixed(0)) != null ? _b2 : "N/A", "s"));
2740
- }))))))), /* @__PURE__ */ React25__namespace.default.createElement(CardFooter, null, onExitQuiz && /* @__PURE__ */ React25__namespace.default.createElement(Button, { variant: "outline", onClick: onExitQuiz, className: "w-full" }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.LogOut, { className: "mr-2 h-4 w-4" }), "Exit Results")));
3678
+ return /* @__PURE__ */ React28__namespace.default.createElement("li", { key: qResult.questionId, className: "p-4 border rounded-md bg-background" }, /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React28__namespace.default.createElement("h4", { className: "font-semibold" }, t("common.questions", { count: index + 1 })), qResult.isCorrect ? /* @__PURE__ */ React28__namespace.default.createElement("span", { className: "text-green-600 font-medium flex items-center" }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.CheckCircle, { className: "mr-1 h-4 w-4" }), " ", t("practiceFlow.results.passed")) : /* @__PURE__ */ React28__namespace.default.createElement("span", { className: "text-destructive font-medium flex items-center" }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.XCircle, { className: "mr-1 h-4 w-4" }), " ", t("practiceFlow.results.failed"))), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React28__namespace.default.createElement("span", { className: "font-medium" }, t("practiceFlow.results.yourAnswer")), " ", getAnswerDisplay(qResult.userAnswer)), !qResult.isCorrect && /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React28__namespace.default.createElement("span", { className: "font-medium" }, t("practiceFlow.results.correctAnswer")), " ", getAnswerDisplay(qResult.correctAnswer)), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, /* @__PURE__ */ React28__namespace.default.createElement("span", { className: "font-medium" }, t("practiceFlow.results.pointsEarned")), " ", qResult.pointsEarned), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, /* @__PURE__ */ React28__namespace.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")));
3679
+ }))))))), /* @__PURE__ */ React28__namespace.default.createElement(CardFooter, { className: "flex flex-col sm:flex-row justify-between gap-2" }, onExitQuiz && /* @__PURE__ */ React28__namespace.default.createElement(Button, { variant: "outline", onClick: onExitQuiz, className: "w-full sm:w-auto" }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.LogOut, { className: "mr-2 h-4 w-4" }), t("common.exit")), showReviewButton && onGenerateReview && /* @__PURE__ */ React28__namespace.default.createElement(
3680
+ Button,
3681
+ {
3682
+ onClick: onGenerateReview,
3683
+ disabled: isReviewLoading,
3684
+ className: "w-full sm:w-auto"
3685
+ },
3686
+ isReviewLoading ? /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.Wand2, { className: "mr-2 h-4 w-4" }),
3687
+ isReviewLoading ? t("practiceFlow.results.generatingReview") : t("practiceFlow.results.generateReview")
3688
+ )));
2741
3689
  };
2742
3690
 
2743
3691
  // src/react-ui/components/ui/QuizPlayer.tsx
2744
3692
  var QuizPlayer = ({ quizConfig, onQuizComplete, onExitQuiz }) => {
2745
3693
  var _a;
2746
- const [engine, setEngine] = React25.useState(null);
2747
- const [currentQuestion, setCurrentQuestion] = React25.useState(null);
2748
- const [currentQuestionNumber, setCurrentQuestionNumber] = React25.useState(0);
2749
- const [totalQuestions, setTotalQuestions] = React25.useState(0);
2750
- const [userAnswer, setUserAnswer] = React25.useState(null);
2751
- const [quizFinished, setQuizFinished] = React25.useState(false);
2752
- const [finalResult, setFinalResult] = React25.useState(null);
2753
- const [timeLeft, setTimeLeft] = React25.useState(null);
2754
- const [isLoading, setIsLoading] = React25.useState(true);
2755
- const [error, setError] = React25.useState(null);
2756
- const programmingQuestionRef = React25.useRef(null);
2757
- const handleAnswerChange = React25.useCallback((answer) => {
3694
+ const [engine, setEngine] = React28.useState(null);
3695
+ const [currentQuestion, setCurrentQuestion] = React28.useState(null);
3696
+ const [currentQuestionNumber, setCurrentQuestionNumber] = React28.useState(0);
3697
+ const [totalQuestions, setTotalQuestions] = React28.useState(0);
3698
+ const [userAnswer, setUserAnswer] = React28.useState(null);
3699
+ const [quizFinished, setQuizFinished] = React28.useState(false);
3700
+ const [finalResult, setFinalResult] = React28.useState(null);
3701
+ const [timeLeft, setTimeLeft] = React28.useState(null);
3702
+ const [isLoading, setIsLoading] = React28.useState(true);
3703
+ const [error, setError] = React28.useState(null);
3704
+ const { t } = reactI18next.useTranslation();
3705
+ const programmingQuestionRef = React28.useRef(null);
3706
+ const engineRef = React28.useRef(null);
3707
+ const isInitializedRef = React28.useRef(false);
3708
+ const callbacks = React28.useMemo(() => ({
3709
+ onQuizStart: (initialData) => {
3710
+ setCurrentQuestion(initialData.initialQuestion);
3711
+ setCurrentQuestionNumber(initialData.currentQuestionNumber);
3712
+ setTotalQuestions(initialData.totalQuestions);
3713
+ setTimeLeft(initialData.timeLimitInSeconds);
3714
+ setIsLoading(false);
3715
+ },
3716
+ onQuestionChange: (question, qNum, total) => {
3717
+ var _a2;
3718
+ setCurrentQuestion(question);
3719
+ setCurrentQuestionNumber(qNum);
3720
+ setTotalQuestions(total);
3721
+ const existingAnswer = (_a2 = engineRef.current) == null ? void 0 : _a2.getUserAnswer((question == null ? void 0 : question.id) || "");
3722
+ setUserAnswer(existingAnswer !== void 0 ? existingAnswer : null);
3723
+ },
3724
+ onQuizFinish: (results) => {
3725
+ setFinalResult(results);
3726
+ setQuizFinished(true);
3727
+ onQuizComplete(results);
3728
+ setIsLoading(false);
3729
+ },
3730
+ onTimeTick: (timeLeftInSeconds) => {
3731
+ setTimeLeft(timeLeftInSeconds);
3732
+ },
3733
+ onQuizTimeUp: () => {
3734
+ setError("Time's up! Your quiz has been submitted automatically.");
3735
+ }
3736
+ }), [onQuizComplete]);
3737
+ const handleAnswerChange = React28.useCallback((answer) => {
2758
3738
  if ((currentQuestion == null ? void 0 : currentQuestion.questionType) !== "blockly_programming" && (currentQuestion == null ? void 0 : currentQuestion.questionType) !== "scratch_programming") {
2759
3739
  setUserAnswer(answer);
2760
3740
  }
2761
- }, [currentQuestion]);
2762
- React25.useEffect(() => {
3741
+ }, [currentQuestion == null ? void 0 : currentQuestion.questionType]);
3742
+ const quizConfigKey = React28.useMemo(() => {
3743
+ return JSON.stringify({
3744
+ id: quizConfig.id,
3745
+ version: quizConfig.version,
3746
+ title: quizConfig.title
3747
+ });
3748
+ }, [quizConfig.id, quizConfig.version, quizConfig.title]);
3749
+ React28.useEffect(() => {
3750
+ if (isInitializedRef.current) {
3751
+ return;
3752
+ }
2763
3753
  setIsLoading(true);
2764
3754
  setError(null);
2765
3755
  setQuizFinished(false);
2766
3756
  setFinalResult(null);
2767
3757
  setUserAnswer(null);
3758
+ isInitializedRef.current = true;
2768
3759
  let localQuizEngine = null;
2769
3760
  try {
2770
- const callbacks = {
2771
- onQuizStart: (initialData) => {
2772
- setCurrentQuestion(initialData.initialQuestion);
2773
- setCurrentQuestionNumber(initialData.currentQuestionNumber);
2774
- setTotalQuestions(initialData.totalQuestions);
2775
- setTimeLeft(initialData.timeLimitInSeconds);
2776
- setIsLoading(false);
2777
- },
2778
- onQuestionChange: (question, qNum, total) => {
2779
- setCurrentQuestion(question);
2780
- setCurrentQuestionNumber(qNum);
2781
- setTotalQuestions(total);
2782
- const existingAnswer = engine == null ? void 0 : engine.getUserAnswer((question == null ? void 0 : question.id) || "");
2783
- setUserAnswer(existingAnswer !== void 0 ? existingAnswer : null);
2784
- },
2785
- onQuizFinish: (results) => {
2786
- setFinalResult(results);
2787
- setQuizFinished(true);
2788
- onQuizComplete(results);
2789
- setIsLoading(false);
2790
- },
2791
- onTimeTick: (timeLeftInSeconds) => {
2792
- setTimeLeft(timeLeftInSeconds);
2793
- },
2794
- onQuizTimeUp: () => {
2795
- setError("Time's up! Your quiz has been submitted automatically.");
2796
- }
2797
- };
2798
3761
  localQuizEngine = new QuizEngine({ config: quizConfig, callbacks });
3762
+ engineRef.current = localQuizEngine;
2799
3763
  setEngine(localQuizEngine);
2800
3764
  const initialQ = localQuizEngine.getCurrentQuestion();
2801
3765
  setCurrentQuestion(initialQ);
@@ -2807,84 +3771,88 @@ var QuizPlayer = ({ quizConfig, onQuizComplete, onExitQuiz }) => {
2807
3771
  setUserAnswer(existingAnswer !== void 0 ? existingAnswer : null);
2808
3772
  }
2809
3773
  setIsLoading(false);
2810
- return () => {
2811
- if (localQuizEngine) {
2812
- localQuizEngine.destroy();
2813
- }
2814
- };
2815
3774
  } catch (e) {
2816
- console.error("Error initializing QuizEngine:", e);
2817
3775
  setError(e instanceof Error ? e.message : "Failed to load quiz.");
2818
3776
  setIsLoading(false);
2819
- if (localQuizEngine) {
2820
- localQuizEngine.destroy();
2821
- }
3777
+ isInitializedRef.current = false;
2822
3778
  }
2823
- }, [quizConfig, onQuizComplete]);
2824
- const handleSubmitAnswer = () => {
3779
+ return () => {
3780
+ if (localQuizEngine) localQuizEngine.destroy();
3781
+ if (engineRef.current) engineRef.current = null;
3782
+ isInitializedRef.current = false;
3783
+ };
3784
+ }, [quizConfigKey, callbacks, quizConfig]);
3785
+ const handleSubmitAnswer = React28.useCallback(() => {
2825
3786
  var _a2;
2826
- if (!engine || !currentQuestion) return;
3787
+ const currentEngine = engineRef.current;
3788
+ if (!currentEngine || !currentQuestion) return;
2827
3789
  let answerToSubmit = null;
2828
3790
  if (currentQuestion.questionType === "blockly_programming" || currentQuestion.questionType === "scratch_programming") {
2829
3791
  if (programmingQuestionRef.current && typeof programmingQuestionRef.current.getWorkspaceXml === "function") {
2830
3792
  answerToSubmit = programmingQuestionRef.current.getWorkspaceXml();
2831
3793
  } else {
2832
- console.warn(`${currentQuestion.questionType} ref not available for submission. Submitting last known answer from engine if any.`);
2833
- answerToSubmit = (_a2 = engine.getUserAnswer(currentQuestion.id)) != null ? _a2 : null;
3794
+ answerToSubmit = (_a2 = currentEngine.getUserAnswer(currentQuestion.id)) != null ? _a2 : null;
2834
3795
  }
2835
3796
  } else {
2836
3797
  answerToSubmit = userAnswer;
2837
3798
  }
2838
3799
  if (answerToSubmit !== void 0) {
2839
- engine.submitAnswer(currentQuestion.id, answerToSubmit);
2840
- }
2841
- };
2842
- const handleNext = () => {
2843
- if (engine) {
2844
- handleSubmitAnswer();
2845
- if (engine.getCurrentQuestionNumber() < engine.getTotalQuestions()) {
2846
- engine.nextQuestion();
2847
- } else {
2848
- handleFinishQuiz();
2849
- }
2850
- }
2851
- };
2852
- const handlePrevious = () => {
2853
- if (engine && engine.getCurrentQuestionNumber() > 1) {
2854
- handleSubmitAnswer();
2855
- engine.previousQuestion();
2856
- }
2857
- };
2858
- const handleFinishQuiz = async () => {
2859
- if (engine) {
2860
- setIsLoading(true);
2861
- handleSubmitAnswer();
2862
- await engine.calculateResults();
2863
- }
2864
- };
2865
- const progressPercent = React25.useMemo(() => {
3800
+ currentEngine.submitAnswer(currentQuestion.id, answerToSubmit);
3801
+ }
3802
+ }, [currentQuestion, userAnswer]);
3803
+ const handleNext = React28.useCallback(() => {
3804
+ const currentEngine = engineRef.current;
3805
+ if (!currentEngine) return;
3806
+ handleSubmitAnswer();
3807
+ if (currentEngine.getCurrentQuestionNumber() < currentEngine.getTotalQuestions()) {
3808
+ currentEngine.nextQuestion();
3809
+ } else {
3810
+ handleFinishQuiz();
3811
+ }
3812
+ }, [handleSubmitAnswer]);
3813
+ const handlePrevious = React28.useCallback(() => {
3814
+ const currentEngine = engineRef.current;
3815
+ if (!currentEngine || currentEngine.getCurrentQuestionNumber() <= 1) return;
3816
+ handleSubmitAnswer();
3817
+ currentEngine.previousQuestion();
3818
+ }, [handleSubmitAnswer]);
3819
+ const handleFinishQuiz = React28.useCallback(async () => {
3820
+ const currentEngine = engineRef.current;
3821
+ if (!currentEngine) return;
3822
+ setIsLoading(true);
3823
+ handleSubmitAnswer();
3824
+ await currentEngine.calculateResults();
3825
+ }, [handleSubmitAnswer]);
3826
+ const progressPercent = React28.useMemo(() => {
2866
3827
  if (totalQuestions === 0) return 0;
2867
3828
  return quizFinished ? 100 : Math.max(0, Math.min(100, (currentQuestionNumber - 1) / totalQuestions * 100));
2868
3829
  }, [currentQuestionNumber, totalQuestions, quizFinished]);
3830
+ const formatTime = React28.useCallback((seconds) => {
3831
+ if (seconds === null) return "-:--";
3832
+ const mins = Math.floor(seconds / 60);
3833
+ const secs = seconds % 60;
3834
+ return `${mins}:${secs < 10 ? "0" : ""}${secs}`;
3835
+ }, []);
2869
3836
  if (isLoading) {
2870
- return /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex flex-col items-center justify-center h-64" }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.Loader2, { className: "h-12 w-12 animate-spin text-primary" }), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "mt-4 text-muted-foreground" }, "Loading Quiz..."));
3837
+ return /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "flex flex-col items-center justify-center h-64" }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.Loader2, { className: "h-12 w-12 animate-spin text-primary" }), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "mt-4 text-muted-foreground" }, t("common.loading")));
2871
3838
  }
2872
3839
  if (error) {
2873
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full max-w-2xl mx-auto shadow-xl" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-destructive flex items-center" }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.AlertCircle, { className: "mr-2 h-6 w-6" }), "Quiz Error")), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React25__namespace.default.createElement("p", null, error)), /* @__PURE__ */ React25__namespace.default.createElement(CardFooter, null, onExitQuiz && /* @__PURE__ */ React25__namespace.default.createElement(Button, { variant: "outline", onClick: onExitQuiz }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.LogOut, { className: "mr-2 h-4 w-4" }), " Exit Quiz")));
3840
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full max-w-2xl mx-auto shadow-xl" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-destructive flex items-center" }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.AlertCircle, { className: "mr-2 h-6 w-6" }), "Quiz Error")), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React28__namespace.default.createElement("p", null, error)), /* @__PURE__ */ React28__namespace.default.createElement(CardFooter, null, onExitQuiz && /* @__PURE__ */ React28__namespace.default.createElement(Button, { variant: "outline", onClick: onExitQuiz }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.LogOut, { className: "mr-2 h-4 w-4" }), " ", t("common.exit"))));
2874
3841
  }
2875
3842
  if (quizFinished && finalResult) {
2876
- return /* @__PURE__ */ React25__namespace.default.createElement(QuizResult, { result: finalResult, onExitQuiz, quizTitle: quizConfig.title });
3843
+ return /* @__PURE__ */ React28__namespace.default.createElement(QuizResult, { result: finalResult, onExitQuiz, quizTitle: quizConfig.title });
2877
3844
  }
2878
3845
  if (!currentQuestion) {
2879
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full max-w-2xl mx-auto shadow-xl" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, null, "Quiz Ended"), /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, null, "No more questions, or quiz not loaded correctly.")), /* @__PURE__ */ React25__namespace.default.createElement(CardFooter, null, onExitQuiz && /* @__PURE__ */ React25__namespace.default.createElement(Button, { variant: "outline", onClick: onExitQuiz }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.LogOut, { className: "mr-2 h-4 w-4" }), " Exit Quiz")));
3846
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full max-w-2xl mx-auto shadow-xl" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, null, "Quiz Ended"), /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, null, "No more questions, or quiz not loaded correctly.")), /* @__PURE__ */ React28__namespace.default.createElement(CardFooter, null, onExitQuiz && /* @__PURE__ */ React28__namespace.default.createElement(Button, { variant: "outline", onClick: onExitQuiz }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.LogOut, { className: "mr-2 h-4 w-4" }), " ", t("common.exit"))));
2880
3847
  }
2881
- const formatTime = (seconds) => {
2882
- if (seconds === null) return "-:--";
2883
- const mins = Math.floor(seconds / 60);
2884
- const secs = seconds % 60;
2885
- return `${mins}:${secs < 10 ? "0" : ""}${secs}`;
2886
- };
2887
- return /* @__PURE__ */ React25__namespace.default.createElement(Card, { className: "w-full max-w-3xl mx-auto shadow-xl" }, /* @__PURE__ */ React25__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React25__namespace.default.createElement(CardTitle, { className: "text-2xl font-headline" }, quizConfig.title), timeLeft !== null && /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "flex items-center text-sm text-muted-foreground" }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.Clock, { className: "mr-1 h-4 w-4" }), "Time Left: ", formatTime(timeLeft))), quizConfig.description && /* @__PURE__ */ React25__namespace.default.createElement(CardDescription, null, quizConfig.description), /* @__PURE__ */ React25__namespace.default.createElement("div", { className: "mt-2" }, /* @__PURE__ */ React25__namespace.default.createElement(Progress, { value: progressPercent, "aria-label": `Quiz progress: ${currentQuestionNumber} of ${totalQuestions} questions`, className: "w-full" }), /* @__PURE__ */ React25__namespace.default.createElement("p", { className: "text-sm text-muted-foreground mt-1 text-right" }, "Question ", currentQuestionNumber, " of ", totalQuestions))), /* @__PURE__ */ React25__namespace.default.createElement(CardContent, { className: "min-h-[200px]" }, /* @__PURE__ */ React25__namespace.default.createElement(
3848
+ return /* @__PURE__ */ React28__namespace.default.createElement(Card, { className: "w-full max-w-3xl mx-auto shadow-xl" }, /* @__PURE__ */ React28__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React28__namespace.default.createElement(CardTitle, { className: "text-2xl font-headline" }, quizConfig.title), timeLeft !== null && /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "flex items-center text-sm text-muted-foreground" }, /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.Clock, { className: "mr-1 h-4 w-4" }), t("practiceFlow.player.timeLeft", { time: formatTime(timeLeft) }))), quizConfig.description && /* @__PURE__ */ React28__namespace.default.createElement(CardDescription, null, quizConfig.description), /* @__PURE__ */ React28__namespace.default.createElement("div", { className: "mt-2" }, /* @__PURE__ */ React28__namespace.default.createElement(
3849
+ Progress,
3850
+ {
3851
+ value: progressPercent,
3852
+ "aria-label": `Quiz progress: ${currentQuestionNumber} of ${totalQuestions} questions`,
3853
+ className: "w-full"
3854
+ }
3855
+ ), /* @__PURE__ */ React28__namespace.default.createElement("p", { className: "text-sm text-muted-foreground mt-1 text-right" }, t("practiceFlow.player.questionProgress", { current: currentQuestionNumber, total: totalQuestions })))), /* @__PURE__ */ React28__namespace.default.createElement(CardContent, { className: "min-h-[200px]" }, /* @__PURE__ */ React28__namespace.default.createElement(
2888
3856
  QuestionRenderer,
2889
3857
  {
2890
3858
  question: currentQuestion,
@@ -2894,7 +3862,25 @@ var QuizPlayer = ({ quizConfig, onQuizComplete, onExitQuiz }) => {
2894
3862
  key: currentQuestion.id,
2895
3863
  ref: currentQuestion.questionType === "blockly_programming" || currentQuestion.questionType === "scratch_programming" ? programmingQuestionRef : null
2896
3864
  }
2897
- )), /* @__PURE__ */ React25__namespace.default.createElement(CardFooter, { className: "flex justify-between items-center" }, /* @__PURE__ */ React25__namespace.default.createElement(Button, { variant: "outline", onClick: handlePrevious, disabled: currentQuestionNumber <= 1 }, /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.ChevronLeft, { className: "mr-2 h-4 w-4" }), " Previous"), onExitQuiz && /* @__PURE__ */ React25__namespace.default.createElement(Button, { variant: "ghost", onClick: onExitQuiz, className: "text-muted-foreground hover:text-destructive" }, "Exit Quiz"), /* @__PURE__ */ React25__namespace.default.createElement(Button, { onClick: handleNext }, currentQuestionNumber === totalQuestions ? "Finish Quiz" : "Next", currentQuestionNumber !== totalQuestions && /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.ChevronRight, { className: "ml-2 h-4 w-4" }), currentQuestionNumber === totalQuestions && /* @__PURE__ */ React25__namespace.default.createElement(lucideReact.CheckCircle, { className: "ml-2 h-4 w-4" }))));
3865
+ )), /* @__PURE__ */ React28__namespace.default.createElement(CardFooter, { className: "flex justify-between items-center" }, /* @__PURE__ */ React28__namespace.default.createElement(
3866
+ Button,
3867
+ {
3868
+ variant: "outline",
3869
+ onClick: handlePrevious,
3870
+ disabled: currentQuestionNumber <= 1
3871
+ },
3872
+ /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.ChevronLeft, { className: "mr-2 h-4 w-4" }),
3873
+ " ",
3874
+ t("common.previous")
3875
+ ), onExitQuiz && /* @__PURE__ */ React28__namespace.default.createElement(
3876
+ Button,
3877
+ {
3878
+ variant: "ghost",
3879
+ onClick: onExitQuiz,
3880
+ className: "text-muted-foreground hover:text-destructive"
3881
+ },
3882
+ t("common.exit")
3883
+ ), /* @__PURE__ */ React28__namespace.default.createElement(Button, { onClick: handleNext }, currentQuestionNumber === totalQuestions ? t("practiceFlow.player.finishQuiz") : t("common.next"), currentQuestionNumber !== totalQuestions && /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.ChevronRight, { className: "ml-2 h-4 w-4" }), currentQuestionNumber === totalQuestions && /* @__PURE__ */ React28__namespace.default.createElement(lucideReact.CheckCircle, { className: "ml-2 h-4 w-4" }))));
2898
3884
  };
2899
3885
 
2900
3886
  // src/player.ts
@@ -2906,7 +3892,7 @@ function mountQuizPlayer(targetElementId, quizConfig) {
2906
3892
  return;
2907
3893
  }
2908
3894
  const AppContainer = () => {
2909
- const [quizResult, setQuizResult] = React25.useState(null);
3895
+ const [quizResult, setQuizResult] = React28.useState(null);
2910
3896
  const handleQuizComplete = (result) => {
2911
3897
  console.log("Quiz Complete (captured inside React AppContainer):", result);
2912
3898
  setQuizResult(result);
@@ -2923,20 +3909,20 @@ function mountQuizPlayer(targetElementId, quizConfig) {
2923
3909
  }
2924
3910
  };
2925
3911
  if (quizResult) {
2926
- return React25__namespace.default.createElement(QuizResult, {
3912
+ return React28__namespace.default.createElement(QuizResult, {
2927
3913
  result: quizResult,
2928
3914
  quizTitle: quizConfig.title,
2929
3915
  onExitQuiz: handleExit
2930
3916
  });
2931
3917
  }
2932
- return React25__namespace.default.createElement(QuizPlayer, {
3918
+ return React28__namespace.default.createElement(QuizPlayer, {
2933
3919
  quizConfig,
2934
3920
  onQuizComplete: handleQuizComplete,
2935
3921
  onExitQuiz: handleExit
2936
3922
  });
2937
3923
  };
2938
3924
  const root = ReactDOM__default.default.createRoot(targetElement);
2939
- root.render(React25__namespace.default.createElement(React25__namespace.default.StrictMode, null, React25__namespace.default.createElement(AppContainer)));
3925
+ root.render(React28__namespace.default.createElement(React28__namespace.default.StrictMode, null, React28__namespace.default.createElement(AppContainer)));
2940
3926
  }
2941
3927
 
2942
3928
  exports.mountQuizPlayer = mountQuizPlayer;