@schoolio/player 1.3.0 → 1.4.1

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/index.js CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  AttemptViewer: () => AttemptViewer,
24
24
  QuizApiClient: () => QuizApiClient,
25
25
  QuizPlayer: () => QuizPlayer,
26
+ TextToSpeech: () => TextToSpeech,
26
27
  calculateScore: () => calculateScore,
27
28
  checkAnswer: () => checkAnswer,
28
29
  createAnswerDetail: () => createAnswerDetail,
@@ -31,7 +32,7 @@ __export(index_exports, {
31
32
  module.exports = __toCommonJS(index_exports);
32
33
 
33
34
  // src/QuizPlayer.tsx
34
- var import_react = require("react");
35
+ var import_react3 = require("react");
35
36
 
36
37
  // src/api.ts
37
38
  var QuizApiClient = class {
@@ -83,6 +84,52 @@ var QuizApiClient = class {
83
84
  `/api/external/quiz-attempts?${queryParams.toString()}`
84
85
  );
85
86
  }
87
+ async skipQuestion(payload) {
88
+ return this.request(
89
+ "POST",
90
+ "/api/external/skip-question",
91
+ payload
92
+ );
93
+ }
94
+ async reportQuestion(payload) {
95
+ return this.request(
96
+ "POST",
97
+ "/api/external/report-question",
98
+ payload
99
+ );
100
+ }
101
+ async getStarterPrompts() {
102
+ return this.request("GET", "/api/external/question-chat/prompts");
103
+ }
104
+ async getOrCreateChatSession(params) {
105
+ return this.request("POST", "/api/external/question-chat/session", params);
106
+ }
107
+ async sendChatMessage(params) {
108
+ return this.request("POST", "/api/external/question-chat/message", params);
109
+ }
110
+ async getChatHistory(questionId, childId) {
111
+ return this.request(
112
+ "GET",
113
+ `/api/external/question-chat/${questionId}/${childId}`
114
+ );
115
+ }
116
+ async getTextToSpeech(text, voice = "nova") {
117
+ const headers = {
118
+ "Content-Type": "application/json"
119
+ };
120
+ if (this.authToken) {
121
+ headers["Authorization"] = `Bearer ${this.authToken}`;
122
+ }
123
+ const response = await fetch(`${this.baseUrl}/api/external/tts`, {
124
+ method: "POST",
125
+ headers,
126
+ body: JSON.stringify({ text, voice })
127
+ });
128
+ if (!response.ok) {
129
+ throw new Error(`TTS request failed: HTTP ${response.status}`);
130
+ }
131
+ return response.blob();
132
+ }
86
133
  };
87
134
 
88
135
  // src/utils.ts
@@ -172,8 +219,813 @@ function formatTime(seconds) {
172
219
  return `${mins}:${secs.toString().padStart(2, "0")}`;
173
220
  }
174
221
 
175
- // src/QuizPlayer.tsx
222
+ // src/TextToSpeech.tsx
223
+ var import_react = require("react");
176
224
  var import_jsx_runtime = require("react/jsx-runtime");
225
+ var styles = {
226
+ container: {
227
+ display: "flex",
228
+ alignItems: "flex-start",
229
+ gap: "8px"
230
+ },
231
+ button: {
232
+ display: "inline-flex",
233
+ alignItems: "center",
234
+ justifyContent: "center",
235
+ border: "none",
236
+ borderRadius: "6px",
237
+ cursor: "pointer",
238
+ transition: "all 0.2s ease",
239
+ flexShrink: 0
240
+ },
241
+ buttonSm: {
242
+ width: "28px",
243
+ height: "28px",
244
+ padding: "4px",
245
+ backgroundColor: "transparent"
246
+ },
247
+ buttonMd: {
248
+ width: "32px",
249
+ height: "32px",
250
+ padding: "6px",
251
+ backgroundColor: "rgba(103, 33, 176, 0.1)"
252
+ },
253
+ buttonPlaying: {
254
+ backgroundColor: "rgba(103, 33, 176, 0.2)"
255
+ },
256
+ buttonDisabled: {
257
+ opacity: 0.4,
258
+ cursor: "not-allowed"
259
+ },
260
+ icon: {
261
+ width: "16px",
262
+ height: "16px",
263
+ color: "#6721b0"
264
+ },
265
+ textContainer: {
266
+ flex: 1
267
+ },
268
+ highlightedWord: {
269
+ backgroundColor: "rgba(103, 33, 176, 0.25)",
270
+ borderRadius: "3px",
271
+ padding: "0 2px",
272
+ transition: "background-color 0.15s ease",
273
+ fontWeight: 500
274
+ }
275
+ };
276
+ function VolumeIcon() {
277
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
278
+ "svg",
279
+ {
280
+ style: styles.icon,
281
+ viewBox: "0 0 24 24",
282
+ fill: "none",
283
+ stroke: "currentColor",
284
+ strokeWidth: "2",
285
+ strokeLinecap: "round",
286
+ strokeLinejoin: "round",
287
+ children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
289
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
291
+ ]
292
+ }
293
+ );
294
+ }
295
+ function StopIcon() {
296
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
297
+ "svg",
298
+ {
299
+ style: styles.icon,
300
+ viewBox: "0 0 24 24",
301
+ fill: "none",
302
+ stroke: "currentColor",
303
+ strokeWidth: "2",
304
+ strokeLinecap: "round",
305
+ strokeLinejoin: "round",
306
+ children: [
307
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
308
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "9", width: "6", height: "6" })
309
+ ]
310
+ }
311
+ );
312
+ }
313
+ function TextToSpeech({ text, inline = false, size = "sm" }) {
314
+ const [isPlaying, setIsPlaying] = (0, import_react.useState)(false);
315
+ const [currentWordIndex, setCurrentWordIndex] = (0, import_react.useState)(-1);
316
+ const [isSupported, setIsSupported] = (0, import_react.useState)(true);
317
+ const [bestVoice, setBestVoice] = (0, import_react.useState)(null);
318
+ const utteranceRef = (0, import_react.useRef)(null);
319
+ const wordsRef = (0, import_react.useRef)([]);
320
+ (0, import_react.useEffect)(() => {
321
+ wordsRef.current = text.split(/\s+/).filter((word) => word.length > 0);
322
+ }, [text]);
323
+ (0, import_react.useEffect)(() => {
324
+ if (typeof window === "undefined" || !("speechSynthesis" in window)) {
325
+ setIsSupported(false);
326
+ }
327
+ }, []);
328
+ (0, import_react.useEffect)(() => {
329
+ if (!isSupported || typeof window === "undefined") return;
330
+ const loadVoices = () => {
331
+ const voices = window.speechSynthesis.getVoices();
332
+ if (voices.length === 0) return;
333
+ const premiumVoices = [
334
+ "samantha",
335
+ "victoria",
336
+ "karen",
337
+ "serena",
338
+ "jenny",
339
+ "aria",
340
+ "emma",
341
+ "moira",
342
+ "fiona",
343
+ "alice"
344
+ ];
345
+ const englishVoices = voices.filter(
346
+ (voice) => voice.lang.startsWith("en-")
347
+ );
348
+ const scoredVoices = englishVoices.map((voice) => {
349
+ let score = 0;
350
+ const nameLower = voice.name.toLowerCase();
351
+ premiumVoices.forEach((premiumName, index) => {
352
+ if (nameLower.includes(premiumName)) {
353
+ score += (premiumVoices.length - index) * 10;
354
+ }
355
+ });
356
+ if (voice.localService) score += 8;
357
+ if (!nameLower.includes("male")) score += 6;
358
+ if (voice.lang === "en-US") score += 3;
359
+ if (nameLower.includes("google us english") || nameLower.includes("microsoft david")) {
360
+ score -= 20;
361
+ }
362
+ return { voice, score };
363
+ });
364
+ scoredVoices.sort((a, b) => b.score - a.score);
365
+ if (scoredVoices.length > 0) {
366
+ setBestVoice(scoredVoices[0].voice);
367
+ }
368
+ };
369
+ loadVoices();
370
+ if (window.speechSynthesis.onvoiceschanged !== void 0) {
371
+ window.speechSynthesis.onvoiceschanged = loadVoices;
372
+ }
373
+ return () => {
374
+ if (window.speechSynthesis.onvoiceschanged !== void 0) {
375
+ window.speechSynthesis.onvoiceschanged = null;
376
+ }
377
+ };
378
+ }, [isSupported]);
379
+ const handlePlay = () => {
380
+ if (!isSupported || typeof window === "undefined") return;
381
+ try {
382
+ if (isPlaying) {
383
+ window.speechSynthesis.cancel();
384
+ setIsPlaying(false);
385
+ setCurrentWordIndex(-1);
386
+ return;
387
+ }
388
+ const utterance = new SpeechSynthesisUtterance(text);
389
+ utteranceRef.current = utterance;
390
+ if (bestVoice) {
391
+ utterance.voice = bestVoice;
392
+ }
393
+ utterance.rate = 1;
394
+ utterance.pitch = 1;
395
+ utterance.volume = 1;
396
+ let wordIndex = 0;
397
+ utterance.onboundary = (event) => {
398
+ if (event.name === "word") {
399
+ setCurrentWordIndex(wordIndex);
400
+ wordIndex++;
401
+ }
402
+ };
403
+ utterance.onstart = () => {
404
+ setIsPlaying(true);
405
+ setCurrentWordIndex(0);
406
+ };
407
+ utterance.onend = () => {
408
+ setIsPlaying(false);
409
+ setCurrentWordIndex(-1);
410
+ };
411
+ utterance.onerror = (event) => {
412
+ console.error("Speech error:", event);
413
+ setIsPlaying(false);
414
+ setCurrentWordIndex(-1);
415
+ };
416
+ window.speechSynthesis.speak(utterance);
417
+ } catch (error) {
418
+ console.error("TTS error:", error);
419
+ setIsPlaying(false);
420
+ }
421
+ };
422
+ (0, import_react.useEffect)(() => {
423
+ return () => {
424
+ if (typeof window !== "undefined" && "speechSynthesis" in window) {
425
+ try {
426
+ window.speechSynthesis.cancel();
427
+ } catch (error) {
428
+ }
429
+ }
430
+ };
431
+ }, []);
432
+ const buttonStyle = {
433
+ ...styles.button,
434
+ ...size === "sm" ? styles.buttonSm : styles.buttonMd,
435
+ ...isPlaying ? styles.buttonPlaying : {},
436
+ ...!isSupported ? styles.buttonDisabled : {}
437
+ };
438
+ if (inline) {
439
+ const words = text.split(/\s+/);
440
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.container, children: [
441
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
442
+ "button",
443
+ {
444
+ style: buttonStyle,
445
+ onClick: handlePlay,
446
+ disabled: !isSupported,
447
+ "aria-label": isPlaying ? "Stop reading" : "Read aloud",
448
+ title: isPlaying ? "Stop" : "Read aloud",
449
+ "data-testid": "button-tts",
450
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StopIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VolumeIcon, {})
451
+ }
452
+ ),
453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.textContainer, children: words.map((word, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
454
+ "span",
455
+ {
456
+ style: index === currentWordIndex ? styles.highlightedWord : void 0,
457
+ children: [
458
+ word,
459
+ index < words.length - 1 ? " " : ""
460
+ ]
461
+ },
462
+ index
463
+ )) })
464
+ ] });
465
+ }
466
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
467
+ "button",
468
+ {
469
+ style: buttonStyle,
470
+ onClick: handlePlay,
471
+ disabled: !isSupported,
472
+ "aria-label": isPlaying ? "Stop reading" : "Read aloud",
473
+ title: isPlaying ? "Stop" : "Read aloud",
474
+ "data-testid": "button-tts",
475
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StopIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VolumeIcon, {})
476
+ }
477
+ );
478
+ }
479
+
480
+ // src/QuestionChatPanel.tsx
481
+ var import_react2 = require("react");
482
+ var import_jsx_runtime2 = require("react/jsx-runtime");
483
+ var panelStyles = {
484
+ container: {
485
+ display: "flex",
486
+ flexDirection: "column",
487
+ height: "100%",
488
+ backgroundColor: "#f8fafc",
489
+ borderRadius: "12px",
490
+ border: "1px solid #e2e8f0",
491
+ overflow: "hidden"
492
+ },
493
+ header: {
494
+ padding: "12px 16px",
495
+ backgroundColor: "#6721b0",
496
+ color: "#ffffff",
497
+ fontWeight: "600",
498
+ fontSize: "14px",
499
+ display: "flex",
500
+ alignItems: "center",
501
+ gap: "8px"
502
+ },
503
+ messagesContainer: {
504
+ flex: 1,
505
+ overflowY: "auto",
506
+ padding: "12px",
507
+ display: "flex",
508
+ flexDirection: "column",
509
+ gap: "8px"
510
+ },
511
+ starterPrompts: {
512
+ display: "flex",
513
+ flexDirection: "column",
514
+ gap: "8px",
515
+ padding: "16px"
516
+ },
517
+ starterButton: {
518
+ padding: "10px 14px",
519
+ borderRadius: "20px",
520
+ border: "1px solid #e2e8f0",
521
+ backgroundColor: "#ffffff",
522
+ color: "#374151",
523
+ fontSize: "13px",
524
+ cursor: "pointer",
525
+ textAlign: "left",
526
+ transition: "all 0.2s ease"
527
+ },
528
+ starterButtonHover: {
529
+ backgroundColor: "#f3e8ff",
530
+ borderColor: "#6721b0"
531
+ },
532
+ messageRow: {
533
+ display: "flex"
534
+ },
535
+ messageContent: {
536
+ display: "flex",
537
+ alignItems: "flex-end",
538
+ gap: "4px",
539
+ maxWidth: "85%"
540
+ },
541
+ userMessage: {
542
+ padding: "10px 14px",
543
+ borderRadius: "16px 16px 4px 16px",
544
+ backgroundColor: "#6721b0",
545
+ color: "#ffffff",
546
+ fontSize: "14px",
547
+ lineHeight: 1.4
548
+ },
549
+ assistantMessage: {
550
+ padding: "10px 14px",
551
+ borderRadius: "16px 16px 16px 4px",
552
+ backgroundColor: "#ffffff",
553
+ color: "#1f2937",
554
+ fontSize: "14px",
555
+ lineHeight: 1.4,
556
+ border: "1px solid #e2e8f0"
557
+ },
558
+ inputContainer: {
559
+ padding: "12px",
560
+ borderTop: "1px solid #e2e8f0",
561
+ backgroundColor: "#ffffff",
562
+ display: "flex",
563
+ gap: "8px",
564
+ alignItems: "center"
565
+ },
566
+ input: {
567
+ flex: 1,
568
+ padding: "10px 14px",
569
+ borderRadius: "20px",
570
+ border: "1px solid #e2e8f0",
571
+ fontSize: "14px",
572
+ outline: "none"
573
+ },
574
+ buttonBase: {
575
+ width: "40px",
576
+ height: "40px",
577
+ borderRadius: "50%",
578
+ border: "none",
579
+ cursor: "pointer",
580
+ display: "flex",
581
+ alignItems: "center",
582
+ justifyContent: "center",
583
+ fontSize: "16px",
584
+ transition: "all 0.2s ease"
585
+ },
586
+ sendButton: {
587
+ backgroundColor: "#6721b0",
588
+ color: "#ffffff"
589
+ },
590
+ sendButtonDisabled: {
591
+ backgroundColor: "#d1d5db",
592
+ cursor: "not-allowed"
593
+ },
594
+ micButton: {
595
+ backgroundColor: "#ffffff",
596
+ border: "1px solid #e2e8f0",
597
+ color: "#374151"
598
+ },
599
+ micButtonActive: {
600
+ backgroundColor: "#ef4444",
601
+ color: "#ffffff",
602
+ border: "none"
603
+ },
604
+ speakButton: {
605
+ width: "28px",
606
+ height: "28px",
607
+ borderRadius: "50%",
608
+ border: "none",
609
+ backgroundColor: "transparent",
610
+ color: "#9ca3af",
611
+ cursor: "pointer",
612
+ display: "flex",
613
+ alignItems: "center",
614
+ justifyContent: "center",
615
+ transition: "all 0.2s ease",
616
+ flexShrink: 0
617
+ },
618
+ speakButtonReady: {
619
+ color: "#22c55e"
620
+ },
621
+ speakButtonPlaying: {
622
+ color: "#6721b0"
623
+ },
624
+ speakButtonLoading: {
625
+ color: "#f97316"
626
+ },
627
+ loadingDots: {
628
+ display: "flex",
629
+ alignItems: "center",
630
+ gap: "4px",
631
+ padding: "10px 14px",
632
+ maxWidth: "85%",
633
+ marginRight: "auto"
634
+ },
635
+ dot: {
636
+ width: "8px",
637
+ height: "8px",
638
+ backgroundColor: "#9ca3af",
639
+ borderRadius: "50%",
640
+ animation: "bounce 1.4s ease-in-out infinite"
641
+ },
642
+ emptyState: {
643
+ flex: 1,
644
+ display: "flex",
645
+ flexDirection: "column",
646
+ alignItems: "center",
647
+ justifyContent: "center",
648
+ padding: "20px",
649
+ color: "#6b7280",
650
+ textAlign: "center"
651
+ },
652
+ helperIcon: {
653
+ width: "48px",
654
+ height: "48px",
655
+ marginBottom: "12px",
656
+ fontSize: "32px"
657
+ }
658
+ };
659
+ var STARTER_PROMPTS = [
660
+ { id: "dont_understand", label: "I don't understand this question", message: "I don't understand this question. Can you help me?" },
661
+ { id: "word_help", label: "I don't understand a word", message: "I don't understand a word in this question. Can you help?" },
662
+ { id: "explain_again", label: "Explain this again", message: "Can you explain this question to me again in a different way?" }
663
+ ];
664
+ var MicIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
665
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }),
666
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
667
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", x2: "12", y1: "19", y2: "22" })
668
+ ] });
669
+ var MicOffIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
670
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "2", x2: "22", y1: "2", y2: "22" }),
671
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18.89 13.23A7.12 7.12 0 0 0 19 12v-2" }),
672
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M5 10v2a7 7 0 0 0 12 5" }),
673
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33" }),
674
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12" }),
675
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", x2: "12", y1: "19", y2: "22" })
676
+ ] });
677
+ var VolumeIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
678
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
679
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
680
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
681
+ ] });
682
+ var SendIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
683
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
684
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
685
+ ] });
686
+ var HelpIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", stroke: "#6721b0", strokeWidth: "2", children: [
687
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
688
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }),
689
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
690
+ ] });
691
+ function QuestionChatPanel({
692
+ apiClient,
693
+ question,
694
+ quizId,
695
+ childId,
696
+ parentId,
697
+ lessonId,
698
+ courseId
699
+ }) {
700
+ const [messages, setMessages] = (0, import_react2.useState)([]);
701
+ const [inputValue, setInputValue] = (0, import_react2.useState)("");
702
+ const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
703
+ const [chatId, setChatId] = (0, import_react2.useState)(null);
704
+ const [hoveredButton, setHoveredButton] = (0, import_react2.useState)(null);
705
+ const [isListening, setIsListening] = (0, import_react2.useState)(false);
706
+ const [speakingIndex, setSpeakingIndex] = (0, import_react2.useState)(null);
707
+ const [audioReadyMap, setAudioReadyMap] = (0, import_react2.useState)(/* @__PURE__ */ new Map());
708
+ const messagesContainerRef = (0, import_react2.useRef)(null);
709
+ const messagesEndRef = (0, import_react2.useRef)(null);
710
+ const recognitionRef = (0, import_react2.useRef)(null);
711
+ const audioRef = (0, import_react2.useRef)(null);
712
+ const audioCacheRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
713
+ const isSpeechSupported = typeof window !== "undefined" && (window.SpeechRecognition || window.webkitSpeechRecognition);
714
+ const scrollToBottom = (0, import_react2.useCallback)(() => {
715
+ if (messagesContainerRef.current) {
716
+ messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
717
+ }
718
+ }, []);
719
+ const preCacheAudio = (0, import_react2.useCallback)(async (text) => {
720
+ if (audioCacheRef.current.has(text)) {
721
+ setAudioReadyMap((prev) => new Map(prev).set(text, true));
722
+ return;
723
+ }
724
+ setAudioReadyMap((prev) => new Map(prev).set(text, false));
725
+ try {
726
+ const audioBlob = await apiClient.getTextToSpeech(text, "nova");
727
+ audioCacheRef.current.set(text, audioBlob);
728
+ setAudioReadyMap((prev) => new Map(prev).set(text, true));
729
+ } catch (error) {
730
+ console.error("Pre-cache TTS error:", error);
731
+ }
732
+ }, [apiClient]);
733
+ const startListening = (0, import_react2.useCallback)(() => {
734
+ const SpeechRecognitionAPI = window.SpeechRecognition || window.webkitSpeechRecognition;
735
+ if (!SpeechRecognitionAPI) {
736
+ console.log("Speech recognition not supported");
737
+ return;
738
+ }
739
+ const recognition = new SpeechRecognitionAPI();
740
+ recognition.continuous = true;
741
+ recognition.interimResults = true;
742
+ recognition.lang = "en-US";
743
+ recognition.onstart = () => {
744
+ console.log("Speech recognition started");
745
+ setIsListening(true);
746
+ };
747
+ recognition.onresult = (event) => {
748
+ let finalTranscript = "";
749
+ for (let i = 0; i < Object.keys(event.results).length; i++) {
750
+ const result = event.results[i];
751
+ if (result && result[0]) {
752
+ finalTranscript += result[0].transcript;
753
+ }
754
+ }
755
+ if (finalTranscript) {
756
+ setInputValue(finalTranscript);
757
+ }
758
+ };
759
+ recognition.onerror = (event) => {
760
+ console.log("Speech recognition error:", event.error);
761
+ if (event.error === "not-allowed") {
762
+ alert("Microphone access was denied. Please allow microphone access and try again.");
763
+ }
764
+ setIsListening(false);
765
+ };
766
+ recognition.onend = () => {
767
+ console.log("Speech recognition ended");
768
+ setIsListening(false);
769
+ };
770
+ recognitionRef.current = recognition;
771
+ try {
772
+ recognition.start();
773
+ } catch (e) {
774
+ console.log("Failed to start recognition:", e);
775
+ setIsListening(false);
776
+ }
777
+ }, []);
778
+ const stopListening = (0, import_react2.useCallback)(() => {
779
+ if (recognitionRef.current) {
780
+ recognitionRef.current.stop();
781
+ setIsListening(false);
782
+ }
783
+ }, []);
784
+ const speakMessage = (0, import_react2.useCallback)(async (text, index) => {
785
+ if (audioRef.current) {
786
+ audioRef.current.pause();
787
+ audioRef.current = null;
788
+ }
789
+ if (speakingIndex === index) {
790
+ setSpeakingIndex(null);
791
+ return;
792
+ }
793
+ setSpeakingIndex(index);
794
+ try {
795
+ let audioBlob;
796
+ const cachedBlob = audioCacheRef.current.get(text);
797
+ if (cachedBlob) {
798
+ audioBlob = cachedBlob;
799
+ } else {
800
+ audioBlob = await apiClient.getTextToSpeech(text, "nova");
801
+ audioCacheRef.current.set(text, audioBlob);
802
+ }
803
+ const audioUrl = URL.createObjectURL(audioBlob);
804
+ const audio = new Audio(audioUrl);
805
+ audioRef.current = audio;
806
+ audio.onended = () => {
807
+ setSpeakingIndex(null);
808
+ URL.revokeObjectURL(audioUrl);
809
+ };
810
+ audio.onerror = () => {
811
+ setSpeakingIndex(null);
812
+ URL.revokeObjectURL(audioUrl);
813
+ };
814
+ await audio.play();
815
+ } catch (error) {
816
+ console.error("TTS error:", error);
817
+ setSpeakingIndex(null);
818
+ }
819
+ }, [speakingIndex, apiClient]);
820
+ (0, import_react2.useEffect)(() => {
821
+ scrollToBottom();
822
+ }, [messages, scrollToBottom]);
823
+ (0, import_react2.useEffect)(() => {
824
+ setMessages([]);
825
+ setChatId(null);
826
+ setInputValue("");
827
+ const loadHistory = async () => {
828
+ try {
829
+ const history = await apiClient.getChatHistory(question.id, childId);
830
+ if (history.chatId && history.messages.length > 0) {
831
+ setChatId(history.chatId);
832
+ setMessages(history.messages);
833
+ history.messages.filter((m) => m.role === "assistant").forEach((m) => preCacheAudio(m.content));
834
+ }
835
+ } catch (err) {
836
+ console.error("Failed to load chat history:", err);
837
+ }
838
+ };
839
+ loadHistory();
840
+ }, [question.id, childId, apiClient, preCacheAudio]);
841
+ const initializeChat = async () => {
842
+ if (chatId) return chatId;
843
+ try {
844
+ const session = await apiClient.getOrCreateChatSession({
845
+ questionId: question.id,
846
+ questionContent: question,
847
+ quizId,
848
+ childId,
849
+ parentId,
850
+ lessonId,
851
+ courseId
852
+ });
853
+ setChatId(session.chatId);
854
+ return session.chatId;
855
+ } catch (err) {
856
+ console.error("Failed to create chat session:", err);
857
+ return null;
858
+ }
859
+ };
860
+ const sendMessage = async (messageText) => {
861
+ if (!messageText.trim() || isLoading) return;
862
+ if (isListening) {
863
+ stopListening();
864
+ }
865
+ setIsLoading(true);
866
+ const userMsg = {
867
+ role: "user",
868
+ content: messageText,
869
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
870
+ };
871
+ setMessages((prev) => [...prev, userMsg]);
872
+ setInputValue("");
873
+ try {
874
+ const currentChatId = await initializeChat();
875
+ if (!currentChatId) {
876
+ throw new Error("Failed to initialize chat");
877
+ }
878
+ const response = await apiClient.sendChatMessage({
879
+ chatId: currentChatId,
880
+ message: messageText,
881
+ questionContext: question,
882
+ childId
883
+ });
884
+ const assistantMessage = response.assistantMessage || {
885
+ role: "assistant",
886
+ content: "I'm here to help!",
887
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
888
+ };
889
+ setMessages((prev) => [...prev, assistantMessage]);
890
+ preCacheAudio(assistantMessage.content);
891
+ } catch (err) {
892
+ console.error("Failed to send message:", err);
893
+ const content = "Sorry, I'm having trouble right now. Please try again!";
894
+ const errorMsg = {
895
+ role: "assistant",
896
+ content,
897
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
898
+ };
899
+ setMessages((prev) => [...prev, errorMsg]);
900
+ preCacheAudio(content);
901
+ } finally {
902
+ setIsLoading(false);
903
+ }
904
+ };
905
+ const handleKeyPress = (e) => {
906
+ if (e.key === "Enter" && !e.shiftKey) {
907
+ e.preventDefault();
908
+ sendMessage(inputValue);
909
+ }
910
+ };
911
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.container, children: [
912
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
913
+ @keyframes bounce {
914
+ 0%, 60%, 100% { transform: translateY(0); }
915
+ 30% { transform: translateY(-4px); }
916
+ }
917
+ @keyframes pulse {
918
+ 0%, 100% { opacity: 1; }
919
+ 50% { opacity: 0.5; }
920
+ }
921
+ ` }),
922
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: panelStyles.header, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Need Help?" }) }),
923
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: messagesContainerRef, style: panelStyles.messagesContainer, children: messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.emptyState, children: [
924
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: panelStyles.helperIcon, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(HelpIcon, {}) }),
925
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "14px", fontWeight: "500", marginBottom: "8px" }, children: "Hi! I'm your question helper" }),
926
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "13px", color: "#9ca3af" }, children: "Ask me if you need help understanding this question" }),
927
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.starterPrompts, marginTop: "16px" }, children: STARTER_PROMPTS.map((prompt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
928
+ "button",
929
+ {
930
+ style: {
931
+ ...panelStyles.starterButton,
932
+ ...hoveredButton === prompt.id ? panelStyles.starterButtonHover : {}
933
+ },
934
+ onMouseEnter: () => setHoveredButton(prompt.id),
935
+ onMouseLeave: () => setHoveredButton(null),
936
+ onClick: () => sendMessage(prompt.message),
937
+ disabled: isLoading,
938
+ "data-testid": `button-chat-starter-${prompt.id}`,
939
+ children: prompt.label
940
+ },
941
+ prompt.id
942
+ )) })
943
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
944
+ messages.map((msg, idx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
945
+ "div",
946
+ {
947
+ style: {
948
+ ...panelStyles.messageRow,
949
+ justifyContent: msg.role === "user" ? "flex-end" : "flex-start"
950
+ },
951
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
952
+ ...panelStyles.messageContent,
953
+ flexDirection: msg.role === "user" ? "row-reverse" : "row"
954
+ }, children: [
955
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: msg.role === "user" ? panelStyles.userMessage : panelStyles.assistantMessage, children: msg.content }),
956
+ msg.role === "assistant" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
957
+ "button",
958
+ {
959
+ style: {
960
+ ...panelStyles.speakButton,
961
+ ...speakingIndex === idx ? panelStyles.speakButtonPlaying : audioReadyMap.get(msg.content) === true ? panelStyles.speakButtonReady : audioReadyMap.get(msg.content) === false ? panelStyles.speakButtonLoading : {},
962
+ ...audioReadyMap.get(msg.content) === false ? { animation: "pulse 1.5s ease-in-out infinite" } : {}
963
+ },
964
+ onClick: () => speakMessage(msg.content, idx),
965
+ "data-testid": `button-speak-${idx}`,
966
+ title: "Listen to this message",
967
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VolumeIcon2, {})
968
+ }
969
+ )
970
+ ] })
971
+ },
972
+ idx
973
+ )),
974
+ isLoading && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.loadingDots, children: [
975
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.dot, animationDelay: "0s" } }),
976
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.dot, animationDelay: "0.2s" } }),
977
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.dot, animationDelay: "0.4s" } })
978
+ ] }),
979
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: messagesEndRef })
980
+ ] }) }),
981
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.inputContainer, children: [
982
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
983
+ "input",
984
+ {
985
+ type: "text",
986
+ value: inputValue,
987
+ onChange: (e) => setInputValue(e.target.value),
988
+ onKeyPress: handleKeyPress,
989
+ placeholder: isListening ? "Listening..." : "Ask about this question...",
990
+ style: panelStyles.input,
991
+ disabled: isLoading || isListening,
992
+ "data-testid": "input-chat-message"
993
+ }
994
+ ),
995
+ isSpeechSupported && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
996
+ "button",
997
+ {
998
+ onClick: isListening ? stopListening : startListening,
999
+ disabled: isLoading,
1000
+ style: {
1001
+ ...panelStyles.buttonBase,
1002
+ ...isListening ? panelStyles.micButtonActive : panelStyles.micButton
1003
+ },
1004
+ "data-testid": "button-voice-input",
1005
+ title: isListening ? "Stop listening" : "Speak your question",
1006
+ children: isListening ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MicOffIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MicIcon, {})
1007
+ }
1008
+ ),
1009
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1010
+ "button",
1011
+ {
1012
+ onClick: () => sendMessage(inputValue),
1013
+ disabled: isLoading || !inputValue.trim(),
1014
+ style: {
1015
+ ...panelStyles.buttonBase,
1016
+ ...panelStyles.sendButton,
1017
+ ...isLoading || !inputValue.trim() ? panelStyles.sendButtonDisabled : {}
1018
+ },
1019
+ "data-testid": "button-send-chat",
1020
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendIcon, {})
1021
+ }
1022
+ )
1023
+ ] })
1024
+ ] });
1025
+ }
1026
+
1027
+ // src/QuizPlayer.tsx
1028
+ var import_jsx_runtime3 = require("react/jsx-runtime");
177
1029
  var defaultStyles = {
178
1030
  container: {
179
1031
  fontFamily: "system-ui, -apple-system, sans-serif",
@@ -231,7 +1083,10 @@ var defaultStyles = {
231
1083
  cursor: "pointer",
232
1084
  transition: "all 0.2s ease",
233
1085
  outline: "none",
234
- backgroundColor: "#ffffff"
1086
+ boxShadow: "none",
1087
+ backgroundColor: "#ffffff",
1088
+ WebkitTapHighlightColor: "transparent",
1089
+ userSelect: "none"
235
1090
  },
236
1091
  optionSelected: {
237
1092
  borderColor: "#6721b0",
@@ -308,6 +1163,19 @@ var defaultStyles = {
308
1163
  gap: "12px",
309
1164
  marginTop: "24px"
310
1165
  },
1166
+ mainLayout: {
1167
+ display: "flex",
1168
+ gap: "24px"
1169
+ },
1170
+ quizContent: {
1171
+ flex: 1,
1172
+ minWidth: 0
1173
+ },
1174
+ chatPanel: {
1175
+ width: "320px",
1176
+ flexShrink: 0,
1177
+ height: "460px"
1178
+ },
311
1179
  timer: {
312
1180
  fontSize: "14px",
313
1181
  color: "#6b7280",
@@ -453,7 +1321,7 @@ var defaultStyles = {
453
1321
  }
454
1322
  };
455
1323
  function Spinner({ size = 16, color = "#ffffff" }) {
456
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1324
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
457
1325
  "span",
458
1326
  {
459
1327
  style: {
@@ -465,7 +1333,7 @@ function Spinner({ size = 16, color = "#ffffff" }) {
465
1333
  borderRadius: "50%",
466
1334
  animation: "spin 0.8s linear infinite"
467
1335
  },
468
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
1336
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
469
1337
  }
470
1338
  );
471
1339
  }
@@ -482,33 +1350,42 @@ function QuizPlayer({
482
1350
  onError,
483
1351
  onProgress,
484
1352
  onGenerateMoreQuestions,
485
- className
1353
+ className,
1354
+ forceNewAttempt = true
486
1355
  }) {
487
- const [quiz, setQuiz] = (0, import_react.useState)(null);
488
- const [attempt, setAttempt] = (0, import_react.useState)(null);
489
- const [currentQuestionIndex, setCurrentQuestionIndex] = (0, import_react.useState)(0);
490
- const [answers, setAnswers] = (0, import_react.useState)(/* @__PURE__ */ new Map());
491
- const [answersDetail, setAnswersDetail] = (0, import_react.useState)([]);
492
- const [isSubmitting, setIsSubmitting] = (0, import_react.useState)(false);
493
- const [isNavigating, setIsNavigating] = (0, import_react.useState)(false);
494
- const [isCompleted, setIsCompleted] = (0, import_react.useState)(false);
495
- const [result, setResult] = (0, import_react.useState)(null);
496
- const [error, setError] = (0, import_react.useState)(null);
497
- const [isLoading, setIsLoading] = (0, import_react.useState)(true);
498
- const [elapsedSeconds, setElapsedSeconds] = (0, import_react.useState)(0);
499
- const [showIntro, setShowIntro] = (0, import_react.useState)(true);
500
- const [timerStarted, setTimerStarted] = (0, import_react.useState)(false);
501
- const [showFeedback, setShowFeedback] = (0, import_react.useState)(false);
502
- const [currentAnswerDetail, setCurrentAnswerDetail] = (0, import_react.useState)(null);
503
- const [extraQuestions, setExtraQuestions] = (0, import_react.useState)([]);
504
- const [isGeneratingExtra, setIsGeneratingExtra] = (0, import_react.useState)(false);
505
- const apiClient = (0, import_react.useRef)(null);
506
- const timerRef = (0, import_react.useRef)(null);
507
- const startTimeRef = (0, import_react.useRef)(0);
508
- (0, import_react.useEffect)(() => {
1356
+ const [quiz, setQuiz] = (0, import_react3.useState)(null);
1357
+ const [attempt, setAttempt] = (0, import_react3.useState)(null);
1358
+ const [currentQuestionIndex, setCurrentQuestionIndex] = (0, import_react3.useState)(0);
1359
+ const [answers, setAnswers] = (0, import_react3.useState)(/* @__PURE__ */ new Map());
1360
+ const [answersDetail, setAnswersDetail] = (0, import_react3.useState)([]);
1361
+ const [isSubmitting, setIsSubmitting] = (0, import_react3.useState)(false);
1362
+ const [isNavigating, setIsNavigating] = (0, import_react3.useState)(false);
1363
+ const [isCompleted, setIsCompleted] = (0, import_react3.useState)(false);
1364
+ const [result, setResult] = (0, import_react3.useState)(null);
1365
+ const [error, setError] = (0, import_react3.useState)(null);
1366
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(true);
1367
+ const [elapsedSeconds, setElapsedSeconds] = (0, import_react3.useState)(0);
1368
+ const [showIntro, setShowIntro] = (0, import_react3.useState)(true);
1369
+ const [timerStarted, setTimerStarted] = (0, import_react3.useState)(false);
1370
+ const [showFeedback, setShowFeedback] = (0, import_react3.useState)(false);
1371
+ const [currentAnswerDetail, setCurrentAnswerDetail] = (0, import_react3.useState)(null);
1372
+ const [extraQuestions, setExtraQuestions] = (0, import_react3.useState)([]);
1373
+ const [isGeneratingExtra, setIsGeneratingExtra] = (0, import_react3.useState)(false);
1374
+ const [showSkipModal, setShowSkipModal] = (0, import_react3.useState)(false);
1375
+ const [skippedQuestionIds, setSkippedQuestionIds] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
1376
+ const [isSkipping, setIsSkipping] = (0, import_react3.useState)(false);
1377
+ const [skipComment, setSkipComment] = (0, import_react3.useState)("");
1378
+ const [selectedSkipReason, setSelectedSkipReason] = (0, import_react3.useState)(null);
1379
+ const [showReportModal, setShowReportModal] = (0, import_react3.useState)(false);
1380
+ const [isReporting, setIsReporting] = (0, import_react3.useState)(false);
1381
+ const [reportComment, setReportComment] = (0, import_react3.useState)("");
1382
+ const apiClient = (0, import_react3.useRef)(null);
1383
+ const timerRef = (0, import_react3.useRef)(null);
1384
+ const startTimeRef = (0, import_react3.useRef)(0);
1385
+ (0, import_react3.useEffect)(() => {
509
1386
  apiClient.current = new QuizApiClient({ baseUrl: apiBaseUrl, authToken });
510
1387
  }, [apiBaseUrl, authToken]);
511
- (0, import_react.useEffect)(() => {
1388
+ (0, import_react3.useEffect)(() => {
512
1389
  async function initialize() {
513
1390
  if (!apiClient.current) return;
514
1391
  try {
@@ -522,10 +1399,11 @@ function QuizPlayer({
522
1399
  assignLessonId,
523
1400
  courseId,
524
1401
  childId,
525
- parentId
1402
+ parentId,
1403
+ forceNew: forceNewAttempt
526
1404
  });
527
1405
  setAttempt(attemptData);
528
- if (attemptData.answers && attemptData.answers.length > 0) {
1406
+ if (!forceNewAttempt && attemptData.answers && attemptData.answers.length > 0) {
529
1407
  setAnswersDetail(attemptData.answers);
530
1408
  const answersMap = /* @__PURE__ */ new Map();
531
1409
  attemptData.answers.forEach((a) => {
@@ -554,8 +1432,8 @@ function QuizPlayer({
554
1432
  }
555
1433
  }
556
1434
  initialize();
557
- }, [quizId, lessonId, assignLessonId, courseId, childId, parentId, onError]);
558
- (0, import_react.useEffect)(() => {
1435
+ }, [quizId, lessonId, assignLessonId, courseId, childId, parentId, forceNewAttempt, onError]);
1436
+ (0, import_react3.useEffect)(() => {
559
1437
  if (timerStarted && !isCompleted && !error) {
560
1438
  startTimeRef.current = Date.now();
561
1439
  timerRef.current = setInterval(() => {
@@ -568,18 +1446,18 @@ function QuizPlayer({
568
1446
  }
569
1447
  };
570
1448
  }, [timerStarted, isCompleted, error]);
571
- const handleStart = (0, import_react.useCallback)(() => {
1449
+ const handleStart = (0, import_react3.useCallback)(() => {
572
1450
  setShowIntro(false);
573
1451
  setTimerStarted(true);
574
1452
  }, []);
575
- (0, import_react.useEffect)(() => {
1453
+ (0, import_react3.useEffect)(() => {
576
1454
  setShowFeedback(false);
577
1455
  setCurrentAnswerDetail(null);
578
1456
  }, [currentQuestionIndex]);
579
- const allQuestions = quiz ? [...quiz.questions, ...extraQuestions] : [];
1457
+ const allQuestions = quiz ? [...quiz.questions, ...extraQuestions].filter((q) => !skippedQuestionIds.has(q.id)) : [];
580
1458
  const totalQuestions = allQuestions.length;
581
1459
  const maxQuestions = 50;
582
- (0, import_react.useEffect)(() => {
1460
+ (0, import_react3.useEffect)(() => {
583
1461
  if (quiz && onProgress) {
584
1462
  onProgress({
585
1463
  currentQuestion: currentQuestionIndex + 1,
@@ -589,11 +1467,11 @@ function QuizPlayer({
589
1467
  }
590
1468
  }, [currentQuestionIndex, answers.size, quiz, onProgress, totalQuestions]);
591
1469
  const currentQuestion = allQuestions[currentQuestionIndex];
592
- const handleAnswerChange = (0, import_react.useCallback)((value) => {
1470
+ const handleAnswerChange = (0, import_react3.useCallback)((value) => {
593
1471
  if (!currentQuestion) return;
594
1472
  setAnswers((prev) => new Map(prev).set(currentQuestion.id, value));
595
1473
  }, [currentQuestion]);
596
- const handleCheckAnswer = (0, import_react.useCallback)(async () => {
1474
+ const handleCheckAnswer = (0, import_react3.useCallback)(async () => {
597
1475
  if (!quiz || !attempt || !currentQuestion || !apiClient.current) return;
598
1476
  const selectedAnswer2 = answers.get(currentQuestion.id);
599
1477
  if (selectedAnswer2 === void 0) return;
@@ -619,7 +1497,7 @@ function QuizPlayer({
619
1497
  }
620
1498
  setShowFeedback(true);
621
1499
  }, [quiz, attempt, currentQuestion, answers, answersDetail]);
622
- const handleContinue = (0, import_react.useCallback)(() => {
1500
+ const handleContinue = (0, import_react3.useCallback)(() => {
623
1501
  if (!quiz) return;
624
1502
  setShowFeedback(false);
625
1503
  setCurrentAnswerDetail(null);
@@ -627,7 +1505,7 @@ function QuizPlayer({
627
1505
  setCurrentQuestionIndex((prev) => prev + 1);
628
1506
  }
629
1507
  }, [quiz, currentQuestionIndex, totalQuestions]);
630
- const handleAddMoreQuestions = (0, import_react.useCallback)(async () => {
1508
+ const handleAddMoreQuestions = (0, import_react3.useCallback)(async () => {
631
1509
  if (!attempt || !onGenerateMoreQuestions || isGeneratingExtra) return;
632
1510
  if (totalQuestions >= maxQuestions) return;
633
1511
  setIsGeneratingExtra(true);
@@ -650,7 +1528,7 @@ function QuizPlayer({
650
1528
  setIsGeneratingExtra(false);
651
1529
  }
652
1530
  }, [attempt, onGenerateMoreQuestions, isGeneratingExtra, totalQuestions, maxQuestions, onError]);
653
- const handleSubmit = (0, import_react.useCallback)(async () => {
1531
+ const handleSubmit = (0, import_react3.useCallback)(async () => {
654
1532
  if (!quiz || !attempt || !apiClient.current) return;
655
1533
  setIsSubmitting(true);
656
1534
  try {
@@ -672,6 +1550,7 @@ function QuizPlayer({
672
1550
  status: "completed",
673
1551
  score: scoreData.score,
674
1552
  correctAnswers: scoreData.correctAnswers,
1553
+ totalQuestions,
675
1554
  timeSpentSeconds: timeSpent
676
1555
  });
677
1556
  setIsCompleted(true);
@@ -696,17 +1575,89 @@ function QuizPlayer({
696
1575
  setIsSubmitting(false);
697
1576
  }
698
1577
  }, [quiz, attempt, currentQuestion, answers, answersDetail, onComplete, onError, totalQuestions, timerStarted, elapsedSeconds]);
1578
+ const isExtraQuestion = currentQuestion && extraQuestions.some((q) => q.id === currentQuestion.id);
1579
+ const handleSkipQuestion = (0, import_react3.useCallback)(async (reason, comment) => {
1580
+ if (!currentQuestion || !apiClient.current || !attempt) return;
1581
+ setIsSkipping(true);
1582
+ try {
1583
+ await apiClient.current.skipQuestion({
1584
+ questionId: currentQuestion.id,
1585
+ questionContent: currentQuestion,
1586
+ skipReason: reason,
1587
+ skipComment: comment?.trim() || void 0,
1588
+ quizId: quiz?.id,
1589
+ attemptId: attempt.id,
1590
+ childId,
1591
+ parentId,
1592
+ lessonId,
1593
+ courseId,
1594
+ assignLessonId
1595
+ });
1596
+ const newSkippedIds = new Set(skippedQuestionIds).add(currentQuestion.id);
1597
+ setSkippedQuestionIds(newSkippedIds);
1598
+ const newExtraQuestions = extraQuestions.filter((q) => q.id !== currentQuestion.id);
1599
+ setExtraQuestions(newExtraQuestions);
1600
+ const newAllQuestions = quiz ? [...quiz.questions, ...newExtraQuestions].filter((q) => !newSkippedIds.has(q.id)) : [];
1601
+ const newTotalQuestions = newAllQuestions.length;
1602
+ if (newTotalQuestions === 0) {
1603
+ setIsCompleted(true);
1604
+ const quizResult = {
1605
+ attemptId: attempt.id,
1606
+ score: 0,
1607
+ correctAnswers: 0,
1608
+ totalQuestions: 0,
1609
+ answers: [],
1610
+ timeSpentSeconds: elapsedSeconds
1611
+ };
1612
+ setResult(quizResult);
1613
+ onComplete?.(quizResult);
1614
+ } else if (currentQuestionIndex >= newTotalQuestions) {
1615
+ setCurrentQuestionIndex(newTotalQuestions - 1);
1616
+ }
1617
+ setShowSkipModal(false);
1618
+ setSkipComment("");
1619
+ setSelectedSkipReason(null);
1620
+ } catch (err) {
1621
+ console.error("Failed to skip question:", err);
1622
+ } finally {
1623
+ setIsSkipping(false);
1624
+ }
1625
+ }, [currentQuestion, apiClient, attempt, quiz, childId, parentId, lessonId, courseId, assignLessonId, skippedQuestionIds, extraQuestions, currentQuestionIndex, elapsedSeconds, onComplete]);
1626
+ const handleReportQuestion = (0, import_react3.useCallback)(async (comment) => {
1627
+ if (!currentQuestion || !apiClient.current || !attempt || !comment.trim()) return;
1628
+ setIsReporting(true);
1629
+ try {
1630
+ await apiClient.current.reportQuestion({
1631
+ questionId: currentQuestion.id,
1632
+ questionContent: currentQuestion,
1633
+ reportComment: comment.trim(),
1634
+ quizId: quiz?.id,
1635
+ attemptId: attempt.id,
1636
+ childId,
1637
+ parentId,
1638
+ lessonId,
1639
+ courseId,
1640
+ assignLessonId
1641
+ });
1642
+ setShowReportModal(false);
1643
+ setReportComment("");
1644
+ } catch (err) {
1645
+ console.error("Failed to report question:", err);
1646
+ } finally {
1647
+ setIsReporting(false);
1648
+ }
1649
+ }, [currentQuestion, apiClient, attempt, quiz, childId, parentId, lessonId, courseId, assignLessonId]);
699
1650
  if (isLoading) {
700
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.loading, children: "Loading quiz..." }) });
1651
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.loading, children: "Loading quiz..." }) });
701
1652
  }
702
1653
  if (error) {
703
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.error, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
1654
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.error, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { children: [
704
1655
  "Error: ",
705
1656
  error
706
1657
  ] }) }) });
707
1658
  }
708
1659
  if (isCompleted && result) {
709
- const percentage = Math.round(result.correctAnswers / result.totalQuestions * 100);
1660
+ const percentage = result.totalQuestions > 0 ? Math.round(result.correctAnswers / result.totalQuestions * 100) : 0;
710
1661
  const getScoreTheme = (pct) => {
711
1662
  if (pct >= 80) {
712
1663
  return {
@@ -761,7 +1712,7 @@ function QuizPlayer({
761
1712
  rotation: Math.random() * 360,
762
1713
  size: 6 + Math.random() * 8
763
1714
  }));
764
- const StarIcon = ({ filled, delay }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1715
+ const StarIcon = ({ filled, delay }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
765
1716
  "svg",
766
1717
  {
767
1718
  width: "36",
@@ -772,7 +1723,7 @@ function QuizPlayer({
772
1723
  animationDelay: `${delay}s`,
773
1724
  opacity: 0
774
1725
  },
775
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1726
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
776
1727
  "path",
777
1728
  {
778
1729
  d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
@@ -800,7 +1751,7 @@ function QuizPlayer({
800
1751
  }
801
1752
  };
802
1753
  const eyeExpr = getEyeExpression();
803
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1754
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
804
1755
  "svg",
805
1756
  {
806
1757
  width: "120",
@@ -810,35 +1761,35 @@ function QuizPlayer({
810
1761
  animation: mood === "celebrating" ? "bounce 0.6s ease-in-out infinite" : "gentleBob 2s ease-in-out infinite"
811
1762
  },
812
1763
  children: [
813
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "50", cy: "60", rx: "35", ry: "30", fill: "#8b5cf6" }),
814
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "50", cy: "65", rx: "25", ry: "20", fill: "#c4b5fd" }),
815
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "50", cy: "35", r: "28", fill: "#a78bfa" }),
816
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "28,15 35,30 22,28", fill: "#7c3aed" }),
817
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "72,15 65,30 78,28", fill: "#7c3aed" }),
818
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "38", cy: "38", rx: "10", ry: "12", fill: "white" }),
819
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "62", cy: "38", rx: "10", ry: "12", fill: "white" }),
820
- eyeExpr.leftEye ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: "38", y: "44", textAnchor: "middle", fontSize: "16", fill: "#1f2937", children: eyeExpr.leftEye }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "38", cy: eyeExpr.pupilY, r: "5", fill: "#1f2937" }),
821
- eyeExpr.rightEye ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: "62", y: "44", textAnchor: "middle", fontSize: "16", fill: "#1f2937", children: eyeExpr.rightEye }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "62", cy: eyeExpr.pupilY, r: "5", fill: "#1f2937" }),
822
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "50,45 45,52 55,52", fill: "#fbbf24" }),
823
- (mood === "celebrating" || mood === "happy") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
824
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "28", cy: "45", rx: "5", ry: "3", fill: "#fda4af", opacity: "0.6" }),
825
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "72", cy: "45", rx: "5", ry: "3", fill: "#fda4af", opacity: "0.6" })
1764
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "50", cy: "60", rx: "35", ry: "30", fill: "#8b5cf6" }),
1765
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "50", cy: "65", rx: "25", ry: "20", fill: "#c4b5fd" }),
1766
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "50", cy: "35", r: "28", fill: "#a78bfa" }),
1767
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "28,15 35,30 22,28", fill: "#7c3aed" }),
1768
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "72,15 65,30 78,28", fill: "#7c3aed" }),
1769
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "38", cy: "38", rx: "10", ry: "12", fill: "white" }),
1770
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "62", cy: "38", rx: "10", ry: "12", fill: "white" }),
1771
+ eyeExpr.leftEye ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: "38", y: "44", textAnchor: "middle", fontSize: "16", fill: "#1f2937", children: eyeExpr.leftEye }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "38", cy: eyeExpr.pupilY, r: "5", fill: "#1f2937" }),
1772
+ eyeExpr.rightEye ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: "62", y: "44", textAnchor: "middle", fontSize: "16", fill: "#1f2937", children: eyeExpr.rightEye }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "62", cy: eyeExpr.pupilY, r: "5", fill: "#1f2937" }),
1773
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "50,45 45,52 55,52", fill: "#fbbf24" }),
1774
+ (mood === "celebrating" || mood === "happy") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1775
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "28", cy: "45", rx: "5", ry: "3", fill: "#fda4af", opacity: "0.6" }),
1776
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "72", cy: "45", rx: "5", ry: "3", fill: "#fda4af", opacity: "0.6" })
826
1777
  ] }),
827
- mood === "celebrating" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
828
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "18", cy: "55", rx: "8", ry: "15", fill: "#7c3aed", transform: "rotate(-30 18 55)" }),
829
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "82", cy: "55", rx: "8", ry: "15", fill: "#7c3aed", transform: "rotate(30 82 55)" })
830
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
831
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "20", cy: "60", rx: "8", ry: "15", fill: "#7c3aed" }),
832
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "80", cy: "60", rx: "8", ry: "15", fill: "#7c3aed" })
1778
+ mood === "celebrating" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1779
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "18", cy: "55", rx: "8", ry: "15", fill: "#7c3aed", transform: "rotate(-30 18 55)" }),
1780
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "82", cy: "55", rx: "8", ry: "15", fill: "#7c3aed", transform: "rotate(30 82 55)" })
1781
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1782
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "20", cy: "60", rx: "8", ry: "15", fill: "#7c3aed" }),
1783
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "80", cy: "60", rx: "8", ry: "15", fill: "#7c3aed" })
833
1784
  ] }),
834
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "40", cy: "88", rx: "8", ry: "4", fill: "#fbbf24" }),
835
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "60", cy: "88", rx: "8", ry: "4", fill: "#fbbf24" })
1785
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "40", cy: "88", rx: "8", ry: "4", fill: "#fbbf24" }),
1786
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "60", cy: "88", rx: "8", ry: "4", fill: "#fbbf24" })
836
1787
  ]
837
1788
  }
838
1789
  );
839
1790
  };
840
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, style: defaultStyles.container, children: [
841
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
1791
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: defaultStyles.container, children: [
1792
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: `
842
1793
  @keyframes confettiFall {
843
1794
  0% {
844
1795
  transform: translateY(-10px) rotate(0deg);
@@ -902,8 +1853,8 @@ function QuizPlayer({
902
1853
  }
903
1854
  }
904
1855
  ` }),
905
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.results, children: [
906
- percentage >= 60 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.confettiContainer, children: confettiPieces.map((piece) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1856
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.results, children: [
1857
+ percentage >= 60 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.confettiContainer, children: confettiPieces.map((piece) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
907
1858
  "div",
908
1859
  {
909
1860
  style: {
@@ -920,15 +1871,15 @@ function QuizPlayer({
920
1871
  },
921
1872
  piece.id
922
1873
  )) }),
923
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...defaultStyles.resultsBackground, background: theme.bgGradient } }),
924
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.resultsContent, children: [
925
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.resultStars, children: [
926
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StarIcon, { filled: theme.stars >= 1, delay: 0.3 }),
927
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StarIcon, { filled: theme.stars >= 2, delay: 0.5 }),
928
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StarIcon, { filled: theme.stars >= 3, delay: 0.7 })
1874
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { ...defaultStyles.resultsBackground, background: theme.bgGradient } }),
1875
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.resultsContent, children: [
1876
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.resultStars, children: [
1877
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StarIcon, { filled: theme.stars >= 1, delay: 0.3 }),
1878
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StarIcon, { filled: theme.stars >= 2, delay: 0.5 }),
1879
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StarIcon, { filled: theme.stars >= 3, delay: 0.7 })
929
1880
  ] }),
930
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MascotOwl, { mood: theme.mascotMood }) }),
931
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1881
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { marginBottom: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MascotOwl, { mood: theme.mascotMood }) }),
1882
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
932
1883
  "div",
933
1884
  {
934
1885
  style: {
@@ -941,7 +1892,7 @@ function QuizPlayer({
941
1892
  opacity: 0,
942
1893
  border: `3px solid ${theme.badgeColor}`
943
1894
  },
944
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1895
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
945
1896
  "span",
946
1897
  {
947
1898
  style: {
@@ -955,7 +1906,7 @@ function QuizPlayer({
955
1906
  )
956
1907
  }
957
1908
  ),
958
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1909
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
959
1910
  "div",
960
1911
  {
961
1912
  style: {
@@ -963,7 +1914,7 @@ function QuizPlayer({
963
1914
  opacity: 0
964
1915
  },
965
1916
  children: [
966
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1917
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
967
1918
  "div",
968
1919
  {
969
1920
  style: {
@@ -980,7 +1931,7 @@ function QuizPlayer({
980
1931
  ]
981
1932
  }
982
1933
  ),
983
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1934
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
984
1935
  "div",
985
1936
  {
986
1937
  style: {
@@ -995,7 +1946,7 @@ function QuizPlayer({
995
1946
  ]
996
1947
  }
997
1948
  ),
998
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...defaultStyles.resultDetails, marginTop: "8px" }, children: [
1949
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { ...defaultStyles.resultDetails, marginTop: "8px" }, children: [
999
1950
  "Time: ",
1000
1951
  formatTime(result.timeSpentSeconds)
1001
1952
  ] })
@@ -1004,16 +1955,16 @@ function QuizPlayer({
1004
1955
  ] });
1005
1956
  }
1006
1957
  if (quiz && showIntro) {
1007
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.intro, children: [
1008
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.introTitle, children: quiz.title }),
1009
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.introSubtitle, children: "Ready to test your knowledge?" }),
1010
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.introQuestionCount, children: [
1958
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.intro, children: [
1959
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.introTitle, children: quiz.title }),
1960
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.introSubtitle, children: "Ready to test your knowledge?" }),
1961
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.introQuestionCount, children: [
1011
1962
  quiz.questions.length,
1012
1963
  " question",
1013
1964
  quiz.questions.length !== 1 ? "s" : "",
1014
1965
  " to answer"
1015
1966
  ] }),
1016
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1967
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1017
1968
  "button",
1018
1969
  {
1019
1970
  style: defaultStyles.startButton,
@@ -1033,7 +1984,7 @@ function QuizPlayer({
1033
1984
  ] }) });
1034
1985
  }
1035
1986
  if (!quiz || !currentQuestion) {
1036
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.error, children: "No quiz data available" }) });
1987
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.error, children: "No quiz data available" }) });
1037
1988
  }
1038
1989
  const selectedAnswer = answers.get(currentQuestion.id);
1039
1990
  const isLastQuestion = currentQuestionIndex === totalQuestions - 1;
@@ -1041,187 +1992,538 @@ function QuizPlayer({
1041
1992
  const remainingSlots = maxQuestions - totalQuestions;
1042
1993
  const questionsToAdd = Math.min(5, remainingSlots);
1043
1994
  const canAddMore = onGenerateMoreQuestions && remainingSlots > 0;
1044
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, style: defaultStyles.container, children: [
1045
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.header, children: [
1046
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.title, children: quiz.title }),
1047
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.progress, children: [
1995
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: defaultStyles.container, children: [
1996
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.header, children: [
1997
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.title, children: quiz.title }),
1998
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.progress, children: [
1048
1999
  "Question ",
1049
2000
  currentQuestionIndex + 1,
1050
2001
  " of ",
1051
2002
  totalQuestions
1052
2003
  ] }),
1053
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
2004
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
1054
2005
  ] }),
1055
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.question, children: [
1056
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.questionText, children: currentQuestion.question }),
1057
- (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
1058
- const isSelected = selectedAnswer === option;
1059
- const isCorrectOption = currentQuestion.correctAnswer === option;
1060
- let optionStyle = { ...defaultStyles.option };
1061
- if (showFeedback) {
1062
- if (isCorrectOption) {
1063
- optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
1064
- } else if (isSelected && !isCorrectOption) {
1065
- optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
1066
- }
1067
- } else if (isSelected) {
1068
- optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
1069
- }
1070
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1071
- "div",
1072
- {
1073
- style: {
1074
- ...optionStyle,
1075
- cursor: showFeedback ? "default" : "pointer"
1076
- },
1077
- onClick: () => !showFeedback && handleAnswerChange(option),
1078
- children: option
1079
- },
1080
- idx
1081
- );
1082
- }) }),
1083
- currentQuestion.type === "multiple" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
1084
- const selected = Array.isArray(selectedAnswer) && selectedAnswer.includes(option);
1085
- const correctAnswers = Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer : currentQuestion.correctAnswer ? [currentQuestion.correctAnswer] : [];
1086
- const isCorrectOption = correctAnswers.includes(option);
1087
- let optionStyle = { ...defaultStyles.option };
1088
- if (showFeedback) {
1089
- if (isCorrectOption) {
1090
- optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
1091
- } else if (selected && !isCorrectOption) {
1092
- optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
1093
- }
1094
- } else if (selected) {
1095
- optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
1096
- }
1097
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1098
- "div",
1099
- {
1100
- style: {
1101
- ...optionStyle,
1102
- cursor: showFeedback ? "default" : "pointer"
1103
- },
1104
- onClick: () => {
1105
- if (showFeedback) return;
1106
- const current = Array.isArray(selectedAnswer) ? selectedAnswer : [];
1107
- if (selected) {
1108
- handleAnswerChange(current.filter((o) => o !== option));
1109
- } else {
1110
- handleAnswerChange([...current, option]);
2006
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.mainLayout, children: [
2007
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.quizContent, children: [
2008
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { ...defaultStyles.question, position: "relative", paddingBottom: "40px" }, children: [
2009
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.questionText, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TextToSpeech, { text: currentQuestion.question, inline: true, size: "md" }) }),
2010
+ isExtraQuestion && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2011
+ "button",
2012
+ {
2013
+ onClick: () => setShowSkipModal(true),
2014
+ title: "Skip question",
2015
+ style: {
2016
+ position: "absolute",
2017
+ bottom: "8px",
2018
+ left: "0",
2019
+ background: "transparent",
2020
+ border: "none",
2021
+ cursor: "pointer",
2022
+ padding: "6px 10px",
2023
+ borderRadius: "6px",
2024
+ color: "#9ca3af",
2025
+ display: "flex",
2026
+ alignItems: "center",
2027
+ justifyContent: "center",
2028
+ gap: "4px",
2029
+ fontSize: "12px",
2030
+ opacity: 0.6,
2031
+ transition: "opacity 0.2s, color 0.2s"
2032
+ },
2033
+ onMouseEnter: (e) => {
2034
+ e.currentTarget.style.opacity = "1";
2035
+ e.currentTarget.style.color = "#6b7280";
2036
+ },
2037
+ onMouseLeave: (e) => {
2038
+ e.currentTarget.style.opacity = "0.6";
2039
+ e.currentTarget.style.color = "#9ca3af";
2040
+ },
2041
+ "data-testid": "button-skip-question",
2042
+ children: [
2043
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2044
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "5 4 15 12 5 20 5 4" }),
2045
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "19", y1: "5", x2: "19", y2: "19" })
2046
+ ] }),
2047
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Skip" })
2048
+ ]
2049
+ }
2050
+ ),
2051
+ !isExtraQuestion && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2052
+ "button",
2053
+ {
2054
+ onClick: () => setShowReportModal(true),
2055
+ title: "Report an issue with this question",
2056
+ style: {
2057
+ position: "absolute",
2058
+ bottom: "8px",
2059
+ left: "0",
2060
+ background: "transparent",
2061
+ border: "none",
2062
+ cursor: "pointer",
2063
+ padding: "6px 10px",
2064
+ borderRadius: "6px",
2065
+ color: "#9ca3af",
2066
+ display: "flex",
2067
+ alignItems: "center",
2068
+ justifyContent: "center",
2069
+ gap: "4px",
2070
+ fontSize: "12px",
2071
+ opacity: 0.6,
2072
+ transition: "opacity 0.2s, color 0.2s"
2073
+ },
2074
+ onMouseEnter: (e) => {
2075
+ e.currentTarget.style.opacity = "1";
2076
+ e.currentTarget.style.color = "#ef4444";
2077
+ },
2078
+ onMouseLeave: (e) => {
2079
+ e.currentTarget.style.opacity = "0.6";
2080
+ e.currentTarget.style.color = "#9ca3af";
2081
+ },
2082
+ "data-testid": "button-report-question",
2083
+ children: [
2084
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2085
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" }),
2086
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "4", y1: "22", x2: "4", y2: "15" })
2087
+ ] }),
2088
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Report" })
2089
+ ]
2090
+ }
2091
+ ),
2092
+ (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
2093
+ const isSelected = selectedAnswer === option;
2094
+ const isCorrectOption = currentQuestion.correctAnswer === option;
2095
+ let optionStyle = { ...defaultStyles.option };
2096
+ if (showFeedback) {
2097
+ if (isCorrectOption) {
2098
+ optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
2099
+ } else if (isSelected && !isCorrectOption) {
2100
+ optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
2101
+ }
2102
+ } else if (isSelected) {
2103
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
2104
+ }
2105
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2106
+ "div",
2107
+ {
2108
+ style: {
2109
+ ...optionStyle,
2110
+ cursor: showFeedback ? "default" : "pointer",
2111
+ display: "flex",
2112
+ alignItems: "center",
2113
+ gap: "8px"
2114
+ },
2115
+ onClick: () => !showFeedback && handleAnswerChange(option),
2116
+ children: [
2117
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TextToSpeech, { text: option, size: "sm" }) }),
2118
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 }, children: option })
2119
+ ]
2120
+ },
2121
+ idx
2122
+ );
2123
+ }) }),
2124
+ currentQuestion.type === "multiple" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
2125
+ const selected = Array.isArray(selectedAnswer) && selectedAnswer.includes(option);
2126
+ const correctAnswers = Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer : currentQuestion.correctAnswer ? [currentQuestion.correctAnswer] : [];
2127
+ const isCorrectOption = correctAnswers.includes(option);
2128
+ let optionStyle = { ...defaultStyles.option };
2129
+ if (showFeedback) {
2130
+ if (isCorrectOption) {
2131
+ optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
2132
+ } else if (selected && !isCorrectOption) {
2133
+ optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
1111
2134
  }
2135
+ } else if (selected) {
2136
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
2137
+ }
2138
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2139
+ "div",
2140
+ {
2141
+ style: {
2142
+ ...optionStyle,
2143
+ cursor: showFeedback ? "default" : "pointer",
2144
+ display: "flex",
2145
+ alignItems: "center",
2146
+ gap: "8px"
2147
+ },
2148
+ onClick: () => {
2149
+ if (showFeedback) return;
2150
+ const current = Array.isArray(selectedAnswer) ? selectedAnswer : [];
2151
+ if (selected) {
2152
+ handleAnswerChange(current.filter((o) => o !== option));
2153
+ } else {
2154
+ handleAnswerChange([...current, option]);
2155
+ }
2156
+ },
2157
+ children: [
2158
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TextToSpeech, { text: option, size: "sm" }) }),
2159
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 }, children: option })
2160
+ ]
2161
+ },
2162
+ idx
2163
+ );
2164
+ }) }),
2165
+ (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2166
+ "textarea",
2167
+ {
2168
+ style: { ...defaultStyles.input, minHeight: currentQuestion.type === "essay" ? "150px" : "60px" },
2169
+ value: selectedAnswer || "",
2170
+ onChange: (e) => handleAnswerChange(e.target.value),
2171
+ placeholder: "Type your answer here...",
2172
+ disabled: showFeedback
2173
+ }
2174
+ ),
2175
+ currentQuestion.type === "fill" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2176
+ "input",
2177
+ {
2178
+ style: defaultStyles.input,
2179
+ value: (Array.isArray(selectedAnswer) ? selectedAnswer[idx] : "") || "",
2180
+ onChange: (e) => {
2181
+ const current = Array.isArray(selectedAnswer) ? [...selectedAnswer] : [];
2182
+ current[idx] = e.target.value;
2183
+ handleAnswerChange(current);
2184
+ },
2185
+ placeholder: `Blank ${idx + 1}`,
2186
+ disabled: showFeedback
1112
2187
  },
1113
- children: option
1114
- },
1115
- idx
1116
- );
1117
- }) }),
1118
- (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1119
- "textarea",
1120
- {
1121
- style: { ...defaultStyles.input, minHeight: currentQuestion.type === "essay" ? "150px" : "60px" },
1122
- value: selectedAnswer || "",
1123
- onChange: (e) => handleAnswerChange(e.target.value),
1124
- placeholder: "Type your answer here...",
1125
- disabled: showFeedback
1126
- }
1127
- ),
1128
- currentQuestion.type === "fill" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1129
- "input",
1130
- {
1131
- style: defaultStyles.input,
1132
- value: (Array.isArray(selectedAnswer) ? selectedAnswer[idx] : "") || "",
1133
- onChange: (e) => {
1134
- const current = Array.isArray(selectedAnswer) ? [...selectedAnswer] : [];
1135
- current[idx] = e.target.value;
1136
- handleAnswerChange(current);
1137
- },
1138
- placeholder: `Blank ${idx + 1}`,
1139
- disabled: showFeedback
1140
- },
1141
- idx
1142
- )) }),
1143
- showFeedback && currentAnswerDetail && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
1144
- ...defaultStyles.feedback,
1145
- ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackCorrect : defaultStyles.feedbackIncorrect
1146
- }, children: [
1147
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
1148
- ...defaultStyles.feedbackTitle,
1149
- ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackTitleCorrect : defaultStyles.feedbackTitleIncorrect
1150
- }, children: currentAnswerDetail.isCorrect ? "\u2713 Correct!" : "\u2717 Incorrect" }),
1151
- currentQuestion.explanation && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.feedbackExplanation, children: currentQuestion.explanation })
1152
- ] })
1153
- ] }),
1154
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.buttonsColumn, children: [
1155
- showFeedback && isLastQuestion && canAddMore && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1156
- "button",
2188
+ idx
2189
+ )) }),
2190
+ showFeedback && currentAnswerDetail && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
2191
+ ...defaultStyles.feedback,
2192
+ ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackCorrect : defaultStyles.feedbackIncorrect
2193
+ }, children: [
2194
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
2195
+ ...defaultStyles.feedbackTitle,
2196
+ ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackTitleCorrect : defaultStyles.feedbackTitleIncorrect
2197
+ }, children: currentAnswerDetail.isCorrect ? "\u2713 Correct!" : "\u2717 Incorrect" }),
2198
+ currentQuestion.explanation && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.feedbackExplanation, children: currentQuestion.explanation })
2199
+ ] })
2200
+ ] }),
2201
+ showSkipModal && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
2202
+ position: "fixed",
2203
+ top: 0,
2204
+ left: 0,
2205
+ right: 0,
2206
+ bottom: 0,
2207
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
2208
+ display: "flex",
2209
+ alignItems: "center",
2210
+ justifyContent: "center",
2211
+ zIndex: 1e3
2212
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
2213
+ backgroundColor: "#ffffff",
2214
+ borderRadius: "12px",
2215
+ padding: "24px",
2216
+ maxWidth: "400px",
2217
+ width: "90%",
2218
+ boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
2219
+ }, children: [
2220
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Skip Question" }),
2221
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "Help us improve by telling us why you're skipping this question." }),
2222
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "8px", marginBottom: "16px" }, children: [
2223
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2224
+ "button",
2225
+ {
2226
+ onClick: () => setSelectedSkipReason("question_issue"),
2227
+ disabled: isSkipping,
2228
+ style: {
2229
+ padding: "12px 16px",
2230
+ borderRadius: "8px",
2231
+ border: selectedSkipReason === "question_issue" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
2232
+ backgroundColor: selectedSkipReason === "question_issue" ? "#f5f3ff" : "#f9fafb",
2233
+ cursor: isSkipping ? "not-allowed" : "pointer",
2234
+ fontSize: "14px",
2235
+ fontWeight: "500",
2236
+ color: "#374151",
2237
+ textAlign: "left",
2238
+ opacity: isSkipping ? 0.6 : 1
2239
+ },
2240
+ "data-testid": "button-skip-reason-issue",
2241
+ children: "Question has an issue"
2242
+ }
2243
+ ),
2244
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2245
+ "button",
2246
+ {
2247
+ onClick: () => setSelectedSkipReason("dont_know"),
2248
+ disabled: isSkipping,
2249
+ style: {
2250
+ padding: "12px 16px",
2251
+ borderRadius: "8px",
2252
+ border: selectedSkipReason === "dont_know" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
2253
+ backgroundColor: selectedSkipReason === "dont_know" ? "#f5f3ff" : "#f9fafb",
2254
+ cursor: isSkipping ? "not-allowed" : "pointer",
2255
+ fontSize: "14px",
2256
+ fontWeight: "500",
2257
+ color: "#374151",
2258
+ textAlign: "left",
2259
+ opacity: isSkipping ? 0.6 : 1
2260
+ },
2261
+ "data-testid": "button-skip-reason-dont-know",
2262
+ children: "I don't know the answer"
2263
+ }
2264
+ )
2265
+ ] }),
2266
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "16px" }, children: [
2267
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { style: { display: "block", fontSize: "13px", fontWeight: "500", color: "#374151", marginBottom: "6px" }, children: "Additional details (optional)" }),
2268
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2269
+ "textarea",
2270
+ {
2271
+ value: skipComment,
2272
+ onChange: (e) => setSkipComment(e.target.value.slice(0, 200)),
2273
+ placeholder: "Tell us more about the issue...",
2274
+ disabled: isSkipping,
2275
+ style: {
2276
+ width: "100%",
2277
+ minHeight: "80px",
2278
+ padding: "10px 12px",
2279
+ borderRadius: "8px",
2280
+ border: "1px solid #e5e7eb",
2281
+ fontSize: "14px",
2282
+ resize: "vertical",
2283
+ fontFamily: "inherit",
2284
+ boxSizing: "border-box"
2285
+ },
2286
+ "data-testid": "input-skip-comment"
2287
+ }
2288
+ ),
2289
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2290
+ skipComment.length,
2291
+ "/200"
2292
+ ] })
2293
+ ] }),
2294
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: "10px" }, children: [
2295
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2296
+ "button",
2297
+ {
2298
+ onClick: () => {
2299
+ setShowSkipModal(false);
2300
+ setSkipComment("");
2301
+ setSelectedSkipReason(null);
2302
+ },
2303
+ style: {
2304
+ flex: 1,
2305
+ padding: "10px 16px",
2306
+ borderRadius: "8px",
2307
+ border: "1px solid #e5e7eb",
2308
+ backgroundColor: "#ffffff",
2309
+ cursor: "pointer",
2310
+ fontSize: "14px",
2311
+ fontWeight: "500",
2312
+ color: "#6b7280"
2313
+ },
2314
+ "data-testid": "button-skip-cancel",
2315
+ children: "Cancel"
2316
+ }
2317
+ ),
2318
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2319
+ "button",
2320
+ {
2321
+ onClick: () => selectedSkipReason && handleSkipQuestion(selectedSkipReason, skipComment),
2322
+ disabled: isSkipping || !selectedSkipReason,
2323
+ style: {
2324
+ flex: 1,
2325
+ padding: "10px 16px",
2326
+ borderRadius: "8px",
2327
+ border: "none",
2328
+ backgroundColor: selectedSkipReason ? "#8b5cf6" : "#d1d5db",
2329
+ cursor: isSkipping || !selectedSkipReason ? "not-allowed" : "pointer",
2330
+ fontSize: "14px",
2331
+ fontWeight: "500",
2332
+ color: "#ffffff",
2333
+ opacity: isSkipping ? 0.6 : 1
2334
+ },
2335
+ "data-testid": "button-skip-submit",
2336
+ children: isSkipping ? "Skipping..." : "Skip Question"
2337
+ }
2338
+ )
2339
+ ] })
2340
+ ] }) }),
2341
+ showReportModal && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
2342
+ position: "fixed",
2343
+ top: 0,
2344
+ left: 0,
2345
+ right: 0,
2346
+ bottom: 0,
2347
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
2348
+ display: "flex",
2349
+ alignItems: "center",
2350
+ justifyContent: "center",
2351
+ zIndex: 1e3
2352
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
2353
+ backgroundColor: "#ffffff",
2354
+ borderRadius: "12px",
2355
+ padding: "24px",
2356
+ maxWidth: "400px",
2357
+ width: "90%",
2358
+ boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
2359
+ }, children: [
2360
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Report Question" }),
2361
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "What's wrong with this question?" }),
2362
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "16px" }, children: [
2363
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2364
+ "textarea",
2365
+ {
2366
+ value: reportComment,
2367
+ onChange: (e) => setReportComment(e.target.value.slice(0, 300)),
2368
+ placeholder: "Describe the issue with this question...",
2369
+ disabled: isReporting,
2370
+ style: {
2371
+ width: "100%",
2372
+ minHeight: "120px",
2373
+ padding: "10px 12px",
2374
+ borderRadius: "8px",
2375
+ border: "1px solid #e5e7eb",
2376
+ fontSize: "14px",
2377
+ resize: "vertical",
2378
+ fontFamily: "inherit",
2379
+ boxSizing: "border-box"
2380
+ },
2381
+ "data-testid": "input-report-comment"
2382
+ }
2383
+ ),
2384
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2385
+ reportComment.length,
2386
+ "/300"
2387
+ ] })
2388
+ ] }),
2389
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: "10px" }, children: [
2390
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2391
+ "button",
2392
+ {
2393
+ onClick: () => {
2394
+ setShowReportModal(false);
2395
+ setReportComment("");
2396
+ },
2397
+ style: {
2398
+ flex: 1,
2399
+ padding: "10px 16px",
2400
+ borderRadius: "8px",
2401
+ border: "1px solid #e5e7eb",
2402
+ backgroundColor: "#ffffff",
2403
+ cursor: "pointer",
2404
+ fontSize: "14px",
2405
+ fontWeight: "500",
2406
+ color: "#6b7280"
2407
+ },
2408
+ "data-testid": "button-report-cancel",
2409
+ children: "Cancel"
2410
+ }
2411
+ ),
2412
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2413
+ "button",
2414
+ {
2415
+ onClick: () => handleReportQuestion(reportComment),
2416
+ disabled: isReporting || !reportComment.trim(),
2417
+ style: {
2418
+ flex: 1,
2419
+ padding: "10px 16px",
2420
+ borderRadius: "8px",
2421
+ border: "none",
2422
+ backgroundColor: reportComment.trim() ? "#ef4444" : "#d1d5db",
2423
+ cursor: isReporting || !reportComment.trim() ? "not-allowed" : "pointer",
2424
+ fontSize: "14px",
2425
+ fontWeight: "500",
2426
+ color: "#ffffff",
2427
+ opacity: isReporting ? 0.6 : 1
2428
+ },
2429
+ "data-testid": "button-report-submit",
2430
+ children: isReporting ? "Reporting..." : "Report"
2431
+ }
2432
+ )
2433
+ ] })
2434
+ ] }) }),
2435
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.buttonsColumn, children: [
2436
+ showFeedback && isLastQuestion && canAddMore && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2437
+ "button",
2438
+ {
2439
+ style: {
2440
+ ...defaultStyles.buttonAddMore,
2441
+ ...isGeneratingExtra ? defaultStyles.buttonAddMoreDisabled : {}
2442
+ },
2443
+ onClick: handleAddMoreQuestions,
2444
+ disabled: isGeneratingExtra,
2445
+ "data-testid": "button-add-more-questions",
2446
+ children: isGeneratingExtra ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2447
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: 16, color: "#9ca3af" }),
2448
+ "Generating Questions..."
2449
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2450
+ "+ Add ",
2451
+ questionsToAdd,
2452
+ " More Question",
2453
+ questionsToAdd !== 1 ? "s" : ""
2454
+ ] })
2455
+ }
2456
+ ),
2457
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { ...defaultStyles.buttons, justifyContent: "flex-end" }, children: showFeedback ? (
2458
+ // After viewing feedback
2459
+ isLastQuestion ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2460
+ "button",
2461
+ {
2462
+ style: {
2463
+ ...defaultStyles.button,
2464
+ ...isSubmitting || isGeneratingExtra ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2465
+ },
2466
+ onClick: handleSubmit,
2467
+ disabled: isSubmitting || isGeneratingExtra,
2468
+ "data-testid": "button-submit-quiz",
2469
+ children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
2470
+ }
2471
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2472
+ "button",
2473
+ {
2474
+ style: {
2475
+ ...defaultStyles.button,
2476
+ ...defaultStyles.buttonPrimary
2477
+ },
2478
+ onClick: handleContinue,
2479
+ "data-testid": "button-continue",
2480
+ children: "Continue"
2481
+ }
2482
+ )
2483
+ ) : (
2484
+ // Before checking answer
2485
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2486
+ "button",
2487
+ {
2488
+ style: {
2489
+ ...defaultStyles.button,
2490
+ ...isNavigating || selectedAnswer === void 0 ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2491
+ },
2492
+ onClick: handleCheckAnswer,
2493
+ disabled: isNavigating || selectedAnswer === void 0,
2494
+ "data-testid": "button-check-answer",
2495
+ children: isNavigating ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Check Answer"
2496
+ }
2497
+ )
2498
+ ) })
2499
+ ] })
2500
+ ] }),
2501
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.chatPanel, children: apiClient.current && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2502
+ QuestionChatPanel,
1157
2503
  {
1158
- style: {
1159
- ...defaultStyles.buttonAddMore,
1160
- ...isGeneratingExtra ? defaultStyles.buttonAddMoreDisabled : {}
2504
+ apiClient: apiClient.current,
2505
+ question: {
2506
+ id: currentQuestion.id,
2507
+ question: currentQuestion.question,
2508
+ type: currentQuestion.type,
2509
+ options: currentQuestion.options,
2510
+ correctAnswer: currentQuestion.correctAnswer,
2511
+ explanation: currentQuestion.explanation
1161
2512
  },
1162
- onClick: handleAddMoreQuestions,
1163
- disabled: isGeneratingExtra,
1164
- "data-testid": "button-add-more-questions",
1165
- children: isGeneratingExtra ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1166
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }),
1167
- "Generating Questions..."
1168
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1169
- "+ Add ",
1170
- questionsToAdd,
1171
- " More Question",
1172
- questionsToAdd !== 1 ? "s" : ""
1173
- ] })
2513
+ quizId: quiz.id,
2514
+ childId,
2515
+ parentId,
2516
+ lessonId,
2517
+ courseId
1174
2518
  }
1175
- ),
1176
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...defaultStyles.buttons, justifyContent: "flex-end" }, children: showFeedback ? (
1177
- // After viewing feedback
1178
- isLastQuestion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1179
- "button",
1180
- {
1181
- style: {
1182
- ...defaultStyles.button,
1183
- ...isSubmitting || isGeneratingExtra ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
1184
- },
1185
- onClick: handleSubmit,
1186
- disabled: isSubmitting || isGeneratingExtra,
1187
- "data-testid": "button-submit-quiz",
1188
- children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
1189
- }
1190
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1191
- "button",
1192
- {
1193
- style: {
1194
- ...defaultStyles.button,
1195
- ...defaultStyles.buttonPrimary
1196
- },
1197
- onClick: handleContinue,
1198
- "data-testid": "button-continue",
1199
- children: "Continue"
1200
- }
1201
- )
1202
- ) : (
1203
- // Before checking answer
1204
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1205
- "button",
1206
- {
1207
- style: {
1208
- ...defaultStyles.button,
1209
- ...isNavigating || selectedAnswer === void 0 ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
1210
- },
1211
- onClick: handleCheckAnswer,
1212
- disabled: isNavigating || selectedAnswer === void 0,
1213
- "data-testid": "button-check-answer",
1214
- children: isNavigating ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Check Answer"
1215
- }
1216
- )
1217
2519
  ) })
1218
2520
  ] })
1219
2521
  ] });
1220
2522
  }
1221
2523
 
1222
2524
  // src/AttemptViewer.tsx
1223
- var import_react2 = require("react");
1224
- var import_jsx_runtime2 = require("react/jsx-runtime");
2525
+ var import_react4 = require("react");
2526
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1225
2527
  var defaultStyles2 = {
1226
2528
  container: {
1227
2529
  fontFamily: "system-ui, -apple-system, sans-serif",
@@ -1401,10 +2703,10 @@ function AttemptViewer({
1401
2703
  showExplanations = true,
1402
2704
  title
1403
2705
  }) {
1404
- const [attempt, setAttempt] = (0, import_react2.useState)(null);
1405
- const [loading, setLoading] = (0, import_react2.useState)(true);
1406
- const [error, setError] = (0, import_react2.useState)(null);
1407
- (0, import_react2.useEffect)(() => {
2706
+ const [attempt, setAttempt] = (0, import_react4.useState)(null);
2707
+ const [loading, setLoading] = (0, import_react4.useState)(true);
2708
+ const [error, setError] = (0, import_react4.useState)(null);
2709
+ (0, import_react4.useEffect)(() => {
1408
2710
  const apiClient = new QuizApiClient({
1409
2711
  baseUrl: apiBaseUrl,
1410
2712
  authToken
@@ -1437,49 +2739,49 @@ function AttemptViewer({
1437
2739
  window.location.reload();
1438
2740
  };
1439
2741
  if (loading) {
1440
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.container, className, children: [
1441
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: spinnerKeyframes }),
1442
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.loading, children: [
1443
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.spinner }),
1444
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { marginTop: "16px", color: "#6b7280" }, children: "Loading attempt..." })
2742
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.container, className, children: [
2743
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: spinnerKeyframes }),
2744
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.loading, children: [
2745
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.spinner }),
2746
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { marginTop: "16px", color: "#6b7280" }, children: "Loading attempt..." })
1445
2747
  ] })
1446
2748
  ] });
1447
2749
  }
1448
2750
  if (error || !attempt) {
1449
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.container, className, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.error, children: [
1450
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "18px", fontWeight: "500" }, children: "Failed to load attempt" }),
1451
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { marginTop: "8px", color: "#6b7280" }, children: error }),
1452
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: defaultStyles2.retryButton, onClick: handleRetry, children: "Try Again" })
2751
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.container, className, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.error, children: [
2752
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { fontSize: "18px", fontWeight: "500" }, children: "Failed to load attempt" }),
2753
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { marginTop: "8px", color: "#6b7280" }, children: error }),
2754
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { style: defaultStyles2.retryButton, onClick: handleRetry, children: "Try Again" })
1453
2755
  ] }) });
1454
2756
  }
1455
2757
  const scorePercentage = attempt.score ?? 0;
1456
2758
  const correctCount = attempt.correctAnswers ?? 0;
1457
2759
  const totalQuestions = attempt.totalQuestions;
1458
2760
  const timeSpent = attempt.timeSpentSeconds ?? 0;
1459
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.container, className, children: [
1460
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: spinnerKeyframes }),
1461
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.header, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryGrid, children: [
1462
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
1463
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
2761
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.container, className, children: [
2762
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: spinnerKeyframes }),
2763
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.header, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryGrid, children: [
2764
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
2765
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
1464
2766
  scorePercentage,
1465
2767
  "%"
1466
2768
  ] }),
1467
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Score" })
2769
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Score" })
1468
2770
  ] }),
1469
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
1470
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
2771
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
2772
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
1471
2773
  correctCount,
1472
2774
  "/",
1473
2775
  totalQuestions
1474
2776
  ] }),
1475
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Correct" })
2777
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Correct" })
1476
2778
  ] }),
1477
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
1478
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryValue, children: formatTime(timeSpent) }),
1479
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Time" })
2779
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
2780
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.summaryValue, children: formatTime(timeSpent) }),
2781
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Time" })
1480
2782
  ] })
1481
2783
  ] }) }),
1482
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.questionsList, children: attempt.answers.map((answer, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2784
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.questionsList, children: attempt.answers.map((answer, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1483
2785
  "div",
1484
2786
  {
1485
2787
  style: {
@@ -1487,12 +2789,12 @@ function AttemptViewer({
1487
2789
  ...answer.isCorrect ? defaultStyles2.questionCardCorrect : defaultStyles2.questionCardIncorrect
1488
2790
  },
1489
2791
  children: [
1490
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.questionHeader, children: [
1491
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: defaultStyles2.questionNumber, children: [
2792
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.questionHeader, children: [
2793
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: defaultStyles2.questionNumber, children: [
1492
2794
  "Question ",
1493
2795
  index + 1
1494
2796
  ] }),
1495
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2797
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1496
2798
  "span",
1497
2799
  {
1498
2800
  style: {
@@ -1503,23 +2805,23 @@ function AttemptViewer({
1503
2805
  }
1504
2806
  )
1505
2807
  ] }),
1506
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.questionText, children: answer.questionText }),
1507
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.answerSection, children: [
1508
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.answerLabel, children: "Your answer:" }),
1509
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.studentAnswer, children: formatAnswer(answer.selectedAnswer) })
2808
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.questionText, children: answer.questionText }),
2809
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.answerSection, children: [
2810
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.answerLabel, children: "Your answer:" }),
2811
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.studentAnswer, children: formatAnswer(answer.selectedAnswer) })
1510
2812
  ] }),
1511
- !answer.isCorrect && answer.correctAnswer && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.answerSection, children: [
1512
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.answerLabel, children: "Correct answer:" }),
1513
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.correctAnswer, children: formatAnswer(answer.correctAnswer) })
2813
+ !answer.isCorrect && answer.correctAnswer && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.answerSection, children: [
2814
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.answerLabel, children: "Correct answer:" }),
2815
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.correctAnswer, children: formatAnswer(answer.correctAnswer) })
1514
2816
  ] }),
1515
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.points, children: [
2817
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.points, children: [
1516
2818
  answer.pointsEarned,
1517
2819
  " / ",
1518
2820
  answer.points,
1519
2821
  " points"
1520
2822
  ] }),
1521
- showExplanations && answer.explanation && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.explanation, children: [
1522
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "Explanation:" }),
2823
+ showExplanations && answer.explanation && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.explanation, children: [
2824
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: "Explanation:" }),
1523
2825
  " ",
1524
2826
  answer.explanation
1525
2827
  ] })
@@ -1534,6 +2836,7 @@ function AttemptViewer({
1534
2836
  AttemptViewer,
1535
2837
  QuizApiClient,
1536
2838
  QuizPlayer,
2839
+ TextToSpeech,
1537
2840
  calculateScore,
1538
2841
  checkAnswer,
1539
2842
  createAnswerDetail,