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