@schoolio/player 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  AttemptViewer: () => AttemptViewer,
24
24
  QuizApiClient: () => QuizApiClient,
25
25
  QuizPlayer: () => QuizPlayer,
26
+ TextToSpeech: () => TextToSpeech,
26
27
  calculateScore: () => calculateScore,
27
28
  checkAnswer: () => checkAnswer,
28
29
  createAnswerDetail: () => createAnswerDetail,
@@ -31,7 +32,7 @@ __export(index_exports, {
31
32
  module.exports = __toCommonJS(index_exports);
32
33
 
33
34
  // src/QuizPlayer.tsx
34
- var import_react = require("react");
35
+ var import_react3 = require("react");
35
36
 
36
37
  // src/api.ts
37
38
  var QuizApiClient = class {
@@ -83,6 +84,35 @@ var QuizApiClient = class {
83
84
  `/api/external/quiz-attempts?${queryParams.toString()}`
84
85
  );
85
86
  }
87
+ async skipQuestion(payload) {
88
+ return this.request(
89
+ "POST",
90
+ "/api/external/skip-question",
91
+ payload
92
+ );
93
+ }
94
+ async reportQuestion(payload) {
95
+ return this.request(
96
+ "POST",
97
+ "/api/external/report-question",
98
+ payload
99
+ );
100
+ }
101
+ async getStarterPrompts() {
102
+ return this.request("GET", "/api/external/question-chat/prompts");
103
+ }
104
+ async getOrCreateChatSession(params) {
105
+ return this.request("POST", "/api/external/question-chat/session", params);
106
+ }
107
+ async sendChatMessage(params) {
108
+ return this.request("POST", "/api/external/question-chat/message", params);
109
+ }
110
+ async getChatHistory(questionId, childId) {
111
+ return this.request(
112
+ "GET",
113
+ `/api/external/question-chat/${questionId}/${childId}`
114
+ );
115
+ }
86
116
  };
87
117
 
88
118
  // src/utils.ts
@@ -172,8 +202,597 @@ function formatTime(seconds) {
172
202
  return `${mins}:${secs.toString().padStart(2, "0")}`;
173
203
  }
174
204
 
175
- // src/QuizPlayer.tsx
205
+ // src/TextToSpeech.tsx
206
+ var import_react = require("react");
176
207
  var import_jsx_runtime = require("react/jsx-runtime");
208
+ var styles = {
209
+ container: {
210
+ display: "flex",
211
+ alignItems: "flex-start",
212
+ gap: "8px"
213
+ },
214
+ button: {
215
+ display: "inline-flex",
216
+ alignItems: "center",
217
+ justifyContent: "center",
218
+ border: "none",
219
+ borderRadius: "6px",
220
+ cursor: "pointer",
221
+ transition: "all 0.2s ease",
222
+ flexShrink: 0
223
+ },
224
+ buttonSm: {
225
+ width: "28px",
226
+ height: "28px",
227
+ padding: "4px",
228
+ backgroundColor: "transparent"
229
+ },
230
+ buttonMd: {
231
+ width: "32px",
232
+ height: "32px",
233
+ padding: "6px",
234
+ backgroundColor: "rgba(103, 33, 176, 0.1)"
235
+ },
236
+ buttonPlaying: {
237
+ backgroundColor: "rgba(103, 33, 176, 0.2)"
238
+ },
239
+ buttonDisabled: {
240
+ opacity: 0.4,
241
+ cursor: "not-allowed"
242
+ },
243
+ icon: {
244
+ width: "16px",
245
+ height: "16px",
246
+ color: "#6721b0"
247
+ },
248
+ textContainer: {
249
+ flex: 1
250
+ },
251
+ highlightedWord: {
252
+ backgroundColor: "rgba(103, 33, 176, 0.25)",
253
+ borderRadius: "3px",
254
+ padding: "0 2px",
255
+ transition: "background-color 0.15s ease",
256
+ fontWeight: 500
257
+ }
258
+ };
259
+ function VolumeIcon() {
260
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
261
+ "svg",
262
+ {
263
+ style: styles.icon,
264
+ viewBox: "0 0 24 24",
265
+ fill: "none",
266
+ stroke: "currentColor",
267
+ strokeWidth: "2",
268
+ strokeLinecap: "round",
269
+ strokeLinejoin: "round",
270
+ children: [
271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
272
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
273
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
274
+ ]
275
+ }
276
+ );
277
+ }
278
+ function StopIcon() {
279
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
280
+ "svg",
281
+ {
282
+ style: styles.icon,
283
+ viewBox: "0 0 24 24",
284
+ fill: "none",
285
+ stroke: "currentColor",
286
+ strokeWidth: "2",
287
+ strokeLinecap: "round",
288
+ strokeLinejoin: "round",
289
+ children: [
290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
291
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "9", width: "6", height: "6" })
292
+ ]
293
+ }
294
+ );
295
+ }
296
+ function TextToSpeech({ text, inline = false, size = "sm" }) {
297
+ const [isPlaying, setIsPlaying] = (0, import_react.useState)(false);
298
+ const [currentWordIndex, setCurrentWordIndex] = (0, import_react.useState)(-1);
299
+ const [isSupported, setIsSupported] = (0, import_react.useState)(true);
300
+ const [bestVoice, setBestVoice] = (0, import_react.useState)(null);
301
+ const utteranceRef = (0, import_react.useRef)(null);
302
+ const wordsRef = (0, import_react.useRef)([]);
303
+ (0, import_react.useEffect)(() => {
304
+ wordsRef.current = text.split(/\s+/).filter((word) => word.length > 0);
305
+ }, [text]);
306
+ (0, import_react.useEffect)(() => {
307
+ if (typeof window === "undefined" || !("speechSynthesis" in window)) {
308
+ setIsSupported(false);
309
+ }
310
+ }, []);
311
+ (0, import_react.useEffect)(() => {
312
+ if (!isSupported || typeof window === "undefined") return;
313
+ const loadVoices = () => {
314
+ const voices = window.speechSynthesis.getVoices();
315
+ if (voices.length === 0) return;
316
+ const premiumVoices = [
317
+ "samantha",
318
+ "victoria",
319
+ "karen",
320
+ "serena",
321
+ "jenny",
322
+ "aria",
323
+ "emma",
324
+ "moira",
325
+ "fiona",
326
+ "alice"
327
+ ];
328
+ const englishVoices = voices.filter(
329
+ (voice) => voice.lang.startsWith("en-")
330
+ );
331
+ const scoredVoices = englishVoices.map((voice) => {
332
+ let score = 0;
333
+ const nameLower = voice.name.toLowerCase();
334
+ premiumVoices.forEach((premiumName, index) => {
335
+ if (nameLower.includes(premiumName)) {
336
+ score += (premiumVoices.length - index) * 10;
337
+ }
338
+ });
339
+ if (voice.localService) score += 8;
340
+ if (!nameLower.includes("male")) score += 6;
341
+ if (voice.lang === "en-US") score += 3;
342
+ if (nameLower.includes("google us english") || nameLower.includes("microsoft david")) {
343
+ score -= 20;
344
+ }
345
+ return { voice, score };
346
+ });
347
+ scoredVoices.sort((a, b) => b.score - a.score);
348
+ if (scoredVoices.length > 0) {
349
+ setBestVoice(scoredVoices[0].voice);
350
+ }
351
+ };
352
+ loadVoices();
353
+ if (window.speechSynthesis.onvoiceschanged !== void 0) {
354
+ window.speechSynthesis.onvoiceschanged = loadVoices;
355
+ }
356
+ return () => {
357
+ if (window.speechSynthesis.onvoiceschanged !== void 0) {
358
+ window.speechSynthesis.onvoiceschanged = null;
359
+ }
360
+ };
361
+ }, [isSupported]);
362
+ const handlePlay = () => {
363
+ if (!isSupported || typeof window === "undefined") return;
364
+ try {
365
+ if (isPlaying) {
366
+ window.speechSynthesis.cancel();
367
+ setIsPlaying(false);
368
+ setCurrentWordIndex(-1);
369
+ return;
370
+ }
371
+ const utterance = new SpeechSynthesisUtterance(text);
372
+ utteranceRef.current = utterance;
373
+ if (bestVoice) {
374
+ utterance.voice = bestVoice;
375
+ }
376
+ utterance.rate = 1;
377
+ utterance.pitch = 1;
378
+ utterance.volume = 1;
379
+ let wordIndex = 0;
380
+ utterance.onboundary = (event) => {
381
+ if (event.name === "word") {
382
+ setCurrentWordIndex(wordIndex);
383
+ wordIndex++;
384
+ }
385
+ };
386
+ utterance.onstart = () => {
387
+ setIsPlaying(true);
388
+ setCurrentWordIndex(0);
389
+ };
390
+ utterance.onend = () => {
391
+ setIsPlaying(false);
392
+ setCurrentWordIndex(-1);
393
+ };
394
+ utterance.onerror = (event) => {
395
+ console.error("Speech error:", event);
396
+ setIsPlaying(false);
397
+ setCurrentWordIndex(-1);
398
+ };
399
+ window.speechSynthesis.speak(utterance);
400
+ } catch (error) {
401
+ console.error("TTS error:", error);
402
+ setIsPlaying(false);
403
+ }
404
+ };
405
+ (0, import_react.useEffect)(() => {
406
+ return () => {
407
+ if (typeof window !== "undefined" && "speechSynthesis" in window) {
408
+ try {
409
+ window.speechSynthesis.cancel();
410
+ } catch (error) {
411
+ }
412
+ }
413
+ };
414
+ }, []);
415
+ const buttonStyle = {
416
+ ...styles.button,
417
+ ...size === "sm" ? styles.buttonSm : styles.buttonMd,
418
+ ...isPlaying ? styles.buttonPlaying : {},
419
+ ...!isSupported ? styles.buttonDisabled : {}
420
+ };
421
+ if (inline) {
422
+ const words = text.split(/\s+/);
423
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.container, children: [
424
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
425
+ "button",
426
+ {
427
+ style: buttonStyle,
428
+ onClick: handlePlay,
429
+ disabled: !isSupported,
430
+ "aria-label": isPlaying ? "Stop reading" : "Read aloud",
431
+ title: isPlaying ? "Stop" : "Read aloud",
432
+ "data-testid": "button-tts",
433
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StopIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VolumeIcon, {})
434
+ }
435
+ ),
436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.textContainer, children: words.map((word, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
437
+ "span",
438
+ {
439
+ style: index === currentWordIndex ? styles.highlightedWord : void 0,
440
+ children: [
441
+ word,
442
+ index < words.length - 1 ? " " : ""
443
+ ]
444
+ },
445
+ index
446
+ )) })
447
+ ] });
448
+ }
449
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
450
+ "button",
451
+ {
452
+ style: buttonStyle,
453
+ onClick: handlePlay,
454
+ disabled: !isSupported,
455
+ "aria-label": isPlaying ? "Stop reading" : "Read aloud",
456
+ title: isPlaying ? "Stop" : "Read aloud",
457
+ "data-testid": "button-tts",
458
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StopIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VolumeIcon, {})
459
+ }
460
+ );
461
+ }
462
+
463
+ // src/QuestionChatPanel.tsx
464
+ var import_react2 = require("react");
465
+ var import_jsx_runtime2 = require("react/jsx-runtime");
466
+ var panelStyles = {
467
+ container: {
468
+ display: "flex",
469
+ flexDirection: "column",
470
+ height: "100%",
471
+ backgroundColor: "#f8fafc",
472
+ borderRadius: "12px",
473
+ border: "1px solid #e2e8f0",
474
+ overflow: "hidden"
475
+ },
476
+ header: {
477
+ padding: "12px 16px",
478
+ backgroundColor: "#6721b0",
479
+ color: "#ffffff",
480
+ fontWeight: "600",
481
+ fontSize: "14px",
482
+ display: "flex",
483
+ alignItems: "center",
484
+ gap: "8px"
485
+ },
486
+ messagesContainer: {
487
+ flex: 1,
488
+ overflowY: "auto",
489
+ padding: "12px",
490
+ display: "flex",
491
+ flexDirection: "column",
492
+ gap: "8px"
493
+ },
494
+ starterPrompts: {
495
+ display: "flex",
496
+ flexDirection: "column",
497
+ gap: "8px",
498
+ padding: "16px"
499
+ },
500
+ starterButton: {
501
+ padding: "10px 14px",
502
+ borderRadius: "20px",
503
+ border: "1px solid #e2e8f0",
504
+ backgroundColor: "#ffffff",
505
+ color: "#374151",
506
+ fontSize: "13px",
507
+ cursor: "pointer",
508
+ textAlign: "left",
509
+ transition: "all 0.2s ease"
510
+ },
511
+ starterButtonHover: {
512
+ backgroundColor: "#f3e8ff",
513
+ borderColor: "#6721b0"
514
+ },
515
+ messageRow: {
516
+ display: "flex"
517
+ },
518
+ userMessage: {
519
+ maxWidth: "85%",
520
+ marginLeft: "auto",
521
+ padding: "10px 14px",
522
+ borderRadius: "16px 16px 4px 16px",
523
+ backgroundColor: "#6721b0",
524
+ color: "#ffffff",
525
+ fontSize: "14px",
526
+ lineHeight: 1.4
527
+ },
528
+ assistantMessage: {
529
+ maxWidth: "85%",
530
+ marginRight: "auto",
531
+ padding: "10px 14px",
532
+ borderRadius: "16px 16px 16px 4px",
533
+ backgroundColor: "#ffffff",
534
+ color: "#1f2937",
535
+ fontSize: "14px",
536
+ lineHeight: 1.4,
537
+ border: "1px solid #e2e8f0"
538
+ },
539
+ inputContainer: {
540
+ padding: "12px",
541
+ borderTop: "1px solid #e2e8f0",
542
+ backgroundColor: "#ffffff",
543
+ display: "flex",
544
+ gap: "8px"
545
+ },
546
+ input: {
547
+ flex: 1,
548
+ padding: "10px 14px",
549
+ borderRadius: "20px",
550
+ border: "1px solid #e2e8f0",
551
+ fontSize: "14px",
552
+ outline: "none"
553
+ },
554
+ sendButton: {
555
+ width: "40px",
556
+ height: "40px",
557
+ borderRadius: "50%",
558
+ border: "none",
559
+ backgroundColor: "#6721b0",
560
+ color: "#ffffff",
561
+ cursor: "pointer",
562
+ display: "flex",
563
+ alignItems: "center",
564
+ justifyContent: "center",
565
+ fontSize: "16px",
566
+ transition: "all 0.2s ease"
567
+ },
568
+ sendButtonDisabled: {
569
+ backgroundColor: "#d1d5db",
570
+ cursor: "not-allowed"
571
+ },
572
+ loadingDots: {
573
+ display: "flex",
574
+ alignItems: "center",
575
+ gap: "4px",
576
+ padding: "10px 14px",
577
+ maxWidth: "85%",
578
+ marginRight: "auto"
579
+ },
580
+ dot: {
581
+ width: "8px",
582
+ height: "8px",
583
+ backgroundColor: "#9ca3af",
584
+ borderRadius: "50%",
585
+ animation: "bounce 1.4s ease-in-out infinite"
586
+ },
587
+ emptyState: {
588
+ flex: 1,
589
+ display: "flex",
590
+ flexDirection: "column",
591
+ alignItems: "center",
592
+ justifyContent: "center",
593
+ padding: "20px",
594
+ color: "#6b7280",
595
+ textAlign: "center"
596
+ },
597
+ helperIcon: {
598
+ width: "48px",
599
+ height: "48px",
600
+ marginBottom: "12px",
601
+ fontSize: "32px"
602
+ }
603
+ };
604
+ var STARTER_PROMPTS = [
605
+ { id: "dont_understand", label: "I don't understand this question", message: "I don't understand this question. Can you help me?" },
606
+ { id: "word_help", label: "I don't understand a word", message: "I don't understand a word in this question. Can you help?" },
607
+ { id: "explain_again", label: "Explain this again", message: "Can you explain this question to me again in a different way?" }
608
+ ];
609
+ function QuestionChatPanel({
610
+ apiClient,
611
+ question,
612
+ quizId,
613
+ childId,
614
+ parentId,
615
+ lessonId,
616
+ courseId
617
+ }) {
618
+ const [messages, setMessages] = (0, import_react2.useState)([]);
619
+ const [inputValue, setInputValue] = (0, import_react2.useState)("");
620
+ const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
621
+ const [chatId, setChatId] = (0, import_react2.useState)(null);
622
+ const [hoveredButton, setHoveredButton] = (0, import_react2.useState)(null);
623
+ const messagesContainerRef = (0, import_react2.useRef)(null);
624
+ const messagesEndRef = (0, import_react2.useRef)(null);
625
+ const scrollToBottom = (0, import_react2.useCallback)(() => {
626
+ if (messagesContainerRef.current) {
627
+ messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
628
+ }
629
+ }, []);
630
+ (0, import_react2.useEffect)(() => {
631
+ scrollToBottom();
632
+ }, [messages, scrollToBottom]);
633
+ (0, import_react2.useEffect)(() => {
634
+ setMessages([]);
635
+ setChatId(null);
636
+ setInputValue("");
637
+ const loadHistory = async () => {
638
+ try {
639
+ const history = await apiClient.getChatHistory(question.id, childId);
640
+ if (history.chatId && history.messages.length > 0) {
641
+ setChatId(history.chatId);
642
+ setMessages(history.messages);
643
+ }
644
+ } catch (err) {
645
+ console.error("Failed to load chat history:", err);
646
+ }
647
+ };
648
+ loadHistory();
649
+ }, [question.id, childId, apiClient]);
650
+ const initializeChat = async () => {
651
+ if (chatId) return chatId;
652
+ try {
653
+ const session = await apiClient.getOrCreateChatSession({
654
+ questionId: question.id,
655
+ questionContent: question,
656
+ quizId,
657
+ childId,
658
+ parentId,
659
+ lessonId,
660
+ courseId
661
+ });
662
+ setChatId(session.chatId);
663
+ return session.chatId;
664
+ } catch (err) {
665
+ console.error("Failed to create chat session:", err);
666
+ return null;
667
+ }
668
+ };
669
+ const sendMessage = async (messageText) => {
670
+ if (!messageText.trim() || isLoading) return;
671
+ setIsLoading(true);
672
+ const userMsg = {
673
+ role: "user",
674
+ content: messageText,
675
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
676
+ };
677
+ setMessages((prev) => [...prev, userMsg]);
678
+ setInputValue("");
679
+ try {
680
+ const currentChatId = await initializeChat();
681
+ if (!currentChatId) {
682
+ throw new Error("Failed to initialize chat");
683
+ }
684
+ const response = await apiClient.sendChatMessage({
685
+ chatId: currentChatId,
686
+ message: messageText,
687
+ questionContext: question,
688
+ childId
689
+ });
690
+ setMessages((prev) => [...prev, response.assistantMessage]);
691
+ } catch (err) {
692
+ console.error("Failed to send message:", err);
693
+ const errorMsg = {
694
+ role: "assistant",
695
+ content: "Sorry, I'm having trouble right now. Please try again!",
696
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
697
+ };
698
+ setMessages((prev) => [...prev, errorMsg]);
699
+ } finally {
700
+ setIsLoading(false);
701
+ }
702
+ };
703
+ const handleKeyPress = (e) => {
704
+ if (e.key === "Enter" && !e.shiftKey) {
705
+ e.preventDefault();
706
+ sendMessage(inputValue);
707
+ }
708
+ };
709
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.container, children: [
710
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
711
+ @keyframes bounce {
712
+ 0%, 60%, 100% { transform: translateY(0); }
713
+ 30% { transform: translateY(-4px); }
714
+ }
715
+ ` }),
716
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: panelStyles.header, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Need Help?" }) }),
717
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: messagesContainerRef, style: panelStyles.messagesContainer, children: messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.emptyState, children: [
718
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: panelStyles.helperIcon, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", stroke: "#6721b0", strokeWidth: "2", children: [
719
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
720
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }),
721
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
722
+ ] }) }),
723
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "14px", fontWeight: "500", marginBottom: "8px" }, children: "Hi! I'm your question helper" }),
724
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "13px", color: "#9ca3af" }, children: "Ask me if you need help understanding this question" }),
725
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.starterPrompts, marginTop: "16px" }, children: STARTER_PROMPTS.map((prompt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
726
+ "button",
727
+ {
728
+ style: {
729
+ ...panelStyles.starterButton,
730
+ ...hoveredButton === prompt.id ? panelStyles.starterButtonHover : {}
731
+ },
732
+ onMouseEnter: () => setHoveredButton(prompt.id),
733
+ onMouseLeave: () => setHoveredButton(null),
734
+ onClick: () => sendMessage(prompt.message),
735
+ disabled: isLoading,
736
+ "data-testid": `button-chat-starter-${prompt.id}`,
737
+ children: prompt.label
738
+ },
739
+ prompt.id
740
+ )) })
741
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
742
+ messages.map((msg, idx) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
743
+ "div",
744
+ {
745
+ style: {
746
+ ...panelStyles.messageRow,
747
+ justifyContent: msg.role === "user" ? "flex-end" : "flex-start"
748
+ },
749
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: msg.role === "user" ? panelStyles.userMessage : panelStyles.assistantMessage, children: msg.content })
750
+ },
751
+ idx
752
+ )),
753
+ isLoading && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.loadingDots, children: [
754
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.dot, animationDelay: "0s" } }),
755
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.dot, animationDelay: "0.2s" } }),
756
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.dot, animationDelay: "0.4s" } })
757
+ ] }),
758
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: messagesEndRef })
759
+ ] }) }),
760
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.inputContainer, children: [
761
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
762
+ "input",
763
+ {
764
+ type: "text",
765
+ value: inputValue,
766
+ onChange: (e) => setInputValue(e.target.value),
767
+ onKeyPress: handleKeyPress,
768
+ placeholder: "Ask about this question...",
769
+ style: panelStyles.input,
770
+ disabled: isLoading,
771
+ "data-testid": "input-chat-message"
772
+ }
773
+ ),
774
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
775
+ "button",
776
+ {
777
+ onClick: () => sendMessage(inputValue),
778
+ disabled: isLoading || !inputValue.trim(),
779
+ style: {
780
+ ...panelStyles.sendButton,
781
+ ...isLoading || !inputValue.trim() ? panelStyles.sendButtonDisabled : {}
782
+ },
783
+ "data-testid": "button-send-chat",
784
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
785
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
786
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
787
+ ] })
788
+ }
789
+ )
790
+ ] })
791
+ ] });
792
+ }
793
+
794
+ // src/QuizPlayer.tsx
795
+ var import_jsx_runtime3 = require("react/jsx-runtime");
177
796
  var defaultStyles = {
178
797
  container: {
179
798
  fontFamily: "system-ui, -apple-system, sans-serif",
@@ -231,7 +850,10 @@ var defaultStyles = {
231
850
  cursor: "pointer",
232
851
  transition: "all 0.2s ease",
233
852
  outline: "none",
234
- backgroundColor: "#ffffff"
853
+ boxShadow: "none",
854
+ backgroundColor: "#ffffff",
855
+ WebkitTapHighlightColor: "transparent",
856
+ userSelect: "none"
235
857
  },
236
858
  optionSelected: {
237
859
  borderColor: "#6721b0",
@@ -308,6 +930,19 @@ var defaultStyles = {
308
930
  gap: "12px",
309
931
  marginTop: "24px"
310
932
  },
933
+ mainLayout: {
934
+ display: "flex",
935
+ gap: "24px"
936
+ },
937
+ quizContent: {
938
+ flex: 1,
939
+ minWidth: 0
940
+ },
941
+ chatPanel: {
942
+ width: "320px",
943
+ flexShrink: 0,
944
+ height: "460px"
945
+ },
311
946
  timer: {
312
947
  fontSize: "14px",
313
948
  color: "#6b7280",
@@ -453,7 +1088,7 @@ var defaultStyles = {
453
1088
  }
454
1089
  };
455
1090
  function Spinner({ size = 16, color = "#ffffff" }) {
456
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1091
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
457
1092
  "span",
458
1093
  {
459
1094
  style: {
@@ -465,7 +1100,7 @@ function Spinner({ size = 16, color = "#ffffff" }) {
465
1100
  borderRadius: "50%",
466
1101
  animation: "spin 0.8s linear infinite"
467
1102
  },
468
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
1103
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
469
1104
  }
470
1105
  );
471
1106
  }
@@ -482,33 +1117,42 @@ function QuizPlayer({
482
1117
  onError,
483
1118
  onProgress,
484
1119
  onGenerateMoreQuestions,
485
- className
1120
+ className,
1121
+ forceNewAttempt = true
486
1122
  }) {
487
- const [quiz, setQuiz] = (0, import_react.useState)(null);
488
- const [attempt, setAttempt] = (0, import_react.useState)(null);
489
- const [currentQuestionIndex, setCurrentQuestionIndex] = (0, import_react.useState)(0);
490
- const [answers, setAnswers] = (0, import_react.useState)(/* @__PURE__ */ new Map());
491
- const [answersDetail, setAnswersDetail] = (0, import_react.useState)([]);
492
- const [isSubmitting, setIsSubmitting] = (0, import_react.useState)(false);
493
- const [isNavigating, setIsNavigating] = (0, import_react.useState)(false);
494
- const [isCompleted, setIsCompleted] = (0, import_react.useState)(false);
495
- const [result, setResult] = (0, import_react.useState)(null);
496
- const [error, setError] = (0, import_react.useState)(null);
497
- const [isLoading, setIsLoading] = (0, import_react.useState)(true);
498
- const [elapsedSeconds, setElapsedSeconds] = (0, import_react.useState)(0);
499
- const [showIntro, setShowIntro] = (0, import_react.useState)(true);
500
- const [timerStarted, setTimerStarted] = (0, import_react.useState)(false);
501
- const [showFeedback, setShowFeedback] = (0, import_react.useState)(false);
502
- const [currentAnswerDetail, setCurrentAnswerDetail] = (0, import_react.useState)(null);
503
- const [extraQuestions, setExtraQuestions] = (0, import_react.useState)([]);
504
- const [isGeneratingExtra, setIsGeneratingExtra] = (0, import_react.useState)(false);
505
- const apiClient = (0, import_react.useRef)(null);
506
- const timerRef = (0, import_react.useRef)(null);
507
- const startTimeRef = (0, import_react.useRef)(0);
508
- (0, import_react.useEffect)(() => {
1123
+ const [quiz, setQuiz] = (0, import_react3.useState)(null);
1124
+ const [attempt, setAttempt] = (0, import_react3.useState)(null);
1125
+ const [currentQuestionIndex, setCurrentQuestionIndex] = (0, import_react3.useState)(0);
1126
+ const [answers, setAnswers] = (0, import_react3.useState)(/* @__PURE__ */ new Map());
1127
+ const [answersDetail, setAnswersDetail] = (0, import_react3.useState)([]);
1128
+ const [isSubmitting, setIsSubmitting] = (0, import_react3.useState)(false);
1129
+ const [isNavigating, setIsNavigating] = (0, import_react3.useState)(false);
1130
+ const [isCompleted, setIsCompleted] = (0, import_react3.useState)(false);
1131
+ const [result, setResult] = (0, import_react3.useState)(null);
1132
+ const [error, setError] = (0, import_react3.useState)(null);
1133
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(true);
1134
+ const [elapsedSeconds, setElapsedSeconds] = (0, import_react3.useState)(0);
1135
+ const [showIntro, setShowIntro] = (0, import_react3.useState)(true);
1136
+ const [timerStarted, setTimerStarted] = (0, import_react3.useState)(false);
1137
+ const [showFeedback, setShowFeedback] = (0, import_react3.useState)(false);
1138
+ const [currentAnswerDetail, setCurrentAnswerDetail] = (0, import_react3.useState)(null);
1139
+ const [extraQuestions, setExtraQuestions] = (0, import_react3.useState)([]);
1140
+ const [isGeneratingExtra, setIsGeneratingExtra] = (0, import_react3.useState)(false);
1141
+ const [showSkipModal, setShowSkipModal] = (0, import_react3.useState)(false);
1142
+ const [skippedQuestionIds, setSkippedQuestionIds] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
1143
+ const [isSkipping, setIsSkipping] = (0, import_react3.useState)(false);
1144
+ const [skipComment, setSkipComment] = (0, import_react3.useState)("");
1145
+ const [selectedSkipReason, setSelectedSkipReason] = (0, import_react3.useState)(null);
1146
+ const [showReportModal, setShowReportModal] = (0, import_react3.useState)(false);
1147
+ const [isReporting, setIsReporting] = (0, import_react3.useState)(false);
1148
+ const [reportComment, setReportComment] = (0, import_react3.useState)("");
1149
+ const apiClient = (0, import_react3.useRef)(null);
1150
+ const timerRef = (0, import_react3.useRef)(null);
1151
+ const startTimeRef = (0, import_react3.useRef)(0);
1152
+ (0, import_react3.useEffect)(() => {
509
1153
  apiClient.current = new QuizApiClient({ baseUrl: apiBaseUrl, authToken });
510
1154
  }, [apiBaseUrl, authToken]);
511
- (0, import_react.useEffect)(() => {
1155
+ (0, import_react3.useEffect)(() => {
512
1156
  async function initialize() {
513
1157
  if (!apiClient.current) return;
514
1158
  try {
@@ -522,10 +1166,11 @@ function QuizPlayer({
522
1166
  assignLessonId,
523
1167
  courseId,
524
1168
  childId,
525
- parentId
1169
+ parentId,
1170
+ forceNew: forceNewAttempt
526
1171
  });
527
1172
  setAttempt(attemptData);
528
- if (attemptData.answers && attemptData.answers.length > 0) {
1173
+ if (!forceNewAttempt && attemptData.answers && attemptData.answers.length > 0) {
529
1174
  setAnswersDetail(attemptData.answers);
530
1175
  const answersMap = /* @__PURE__ */ new Map();
531
1176
  attemptData.answers.forEach((a) => {
@@ -554,8 +1199,8 @@ function QuizPlayer({
554
1199
  }
555
1200
  }
556
1201
  initialize();
557
- }, [quizId, lessonId, assignLessonId, courseId, childId, parentId, onError]);
558
- (0, import_react.useEffect)(() => {
1202
+ }, [quizId, lessonId, assignLessonId, courseId, childId, parentId, forceNewAttempt, onError]);
1203
+ (0, import_react3.useEffect)(() => {
559
1204
  if (timerStarted && !isCompleted && !error) {
560
1205
  startTimeRef.current = Date.now();
561
1206
  timerRef.current = setInterval(() => {
@@ -568,18 +1213,18 @@ function QuizPlayer({
568
1213
  }
569
1214
  };
570
1215
  }, [timerStarted, isCompleted, error]);
571
- const handleStart = (0, import_react.useCallback)(() => {
1216
+ const handleStart = (0, import_react3.useCallback)(() => {
572
1217
  setShowIntro(false);
573
1218
  setTimerStarted(true);
574
1219
  }, []);
575
- (0, import_react.useEffect)(() => {
1220
+ (0, import_react3.useEffect)(() => {
576
1221
  setShowFeedback(false);
577
1222
  setCurrentAnswerDetail(null);
578
1223
  }, [currentQuestionIndex]);
579
- const allQuestions = quiz ? [...quiz.questions, ...extraQuestions] : [];
1224
+ const allQuestions = quiz ? [...quiz.questions, ...extraQuestions].filter((q) => !skippedQuestionIds.has(q.id)) : [];
580
1225
  const totalQuestions = allQuestions.length;
581
1226
  const maxQuestions = 50;
582
- (0, import_react.useEffect)(() => {
1227
+ (0, import_react3.useEffect)(() => {
583
1228
  if (quiz && onProgress) {
584
1229
  onProgress({
585
1230
  currentQuestion: currentQuestionIndex + 1,
@@ -589,11 +1234,11 @@ function QuizPlayer({
589
1234
  }
590
1235
  }, [currentQuestionIndex, answers.size, quiz, onProgress, totalQuestions]);
591
1236
  const currentQuestion = allQuestions[currentQuestionIndex];
592
- const handleAnswerChange = (0, import_react.useCallback)((value) => {
1237
+ const handleAnswerChange = (0, import_react3.useCallback)((value) => {
593
1238
  if (!currentQuestion) return;
594
1239
  setAnswers((prev) => new Map(prev).set(currentQuestion.id, value));
595
1240
  }, [currentQuestion]);
596
- const handleCheckAnswer = (0, import_react.useCallback)(async () => {
1241
+ const handleCheckAnswer = (0, import_react3.useCallback)(async () => {
597
1242
  if (!quiz || !attempt || !currentQuestion || !apiClient.current) return;
598
1243
  const selectedAnswer2 = answers.get(currentQuestion.id);
599
1244
  if (selectedAnswer2 === void 0) return;
@@ -619,7 +1264,7 @@ function QuizPlayer({
619
1264
  }
620
1265
  setShowFeedback(true);
621
1266
  }, [quiz, attempt, currentQuestion, answers, answersDetail]);
622
- const handleContinue = (0, import_react.useCallback)(() => {
1267
+ const handleContinue = (0, import_react3.useCallback)(() => {
623
1268
  if (!quiz) return;
624
1269
  setShowFeedback(false);
625
1270
  setCurrentAnswerDetail(null);
@@ -627,7 +1272,7 @@ function QuizPlayer({
627
1272
  setCurrentQuestionIndex((prev) => prev + 1);
628
1273
  }
629
1274
  }, [quiz, currentQuestionIndex, totalQuestions]);
630
- const handleAddMoreQuestions = (0, import_react.useCallback)(async () => {
1275
+ const handleAddMoreQuestions = (0, import_react3.useCallback)(async () => {
631
1276
  if (!attempt || !onGenerateMoreQuestions || isGeneratingExtra) return;
632
1277
  if (totalQuestions >= maxQuestions) return;
633
1278
  setIsGeneratingExtra(true);
@@ -650,7 +1295,7 @@ function QuizPlayer({
650
1295
  setIsGeneratingExtra(false);
651
1296
  }
652
1297
  }, [attempt, onGenerateMoreQuestions, isGeneratingExtra, totalQuestions, maxQuestions, onError]);
653
- const handleSubmit = (0, import_react.useCallback)(async () => {
1298
+ const handleSubmit = (0, import_react3.useCallback)(async () => {
654
1299
  if (!quiz || !attempt || !apiClient.current) return;
655
1300
  setIsSubmitting(true);
656
1301
  try {
@@ -672,6 +1317,7 @@ function QuizPlayer({
672
1317
  status: "completed",
673
1318
  score: scoreData.score,
674
1319
  correctAnswers: scoreData.correctAnswers,
1320
+ totalQuestions,
675
1321
  timeSpentSeconds: timeSpent
676
1322
  });
677
1323
  setIsCompleted(true);
@@ -696,17 +1342,89 @@ function QuizPlayer({
696
1342
  setIsSubmitting(false);
697
1343
  }
698
1344
  }, [quiz, attempt, currentQuestion, answers, answersDetail, onComplete, onError, totalQuestions, timerStarted, elapsedSeconds]);
1345
+ const isExtraQuestion = currentQuestion && extraQuestions.some((q) => q.id === currentQuestion.id);
1346
+ const handleSkipQuestion = (0, import_react3.useCallback)(async (reason, comment) => {
1347
+ if (!currentQuestion || !apiClient.current || !attempt) return;
1348
+ setIsSkipping(true);
1349
+ try {
1350
+ await apiClient.current.skipQuestion({
1351
+ questionId: currentQuestion.id,
1352
+ questionContent: currentQuestion,
1353
+ skipReason: reason,
1354
+ skipComment: comment?.trim() || void 0,
1355
+ quizId: quiz?.id,
1356
+ attemptId: attempt.id,
1357
+ childId,
1358
+ parentId,
1359
+ lessonId,
1360
+ courseId,
1361
+ assignLessonId
1362
+ });
1363
+ const newSkippedIds = new Set(skippedQuestionIds).add(currentQuestion.id);
1364
+ setSkippedQuestionIds(newSkippedIds);
1365
+ const newExtraQuestions = extraQuestions.filter((q) => q.id !== currentQuestion.id);
1366
+ setExtraQuestions(newExtraQuestions);
1367
+ const newAllQuestions = quiz ? [...quiz.questions, ...newExtraQuestions].filter((q) => !newSkippedIds.has(q.id)) : [];
1368
+ const newTotalQuestions = newAllQuestions.length;
1369
+ if (newTotalQuestions === 0) {
1370
+ setIsCompleted(true);
1371
+ const quizResult = {
1372
+ attemptId: attempt.id,
1373
+ score: 0,
1374
+ correctAnswers: 0,
1375
+ totalQuestions: 0,
1376
+ answers: [],
1377
+ timeSpentSeconds: elapsedSeconds
1378
+ };
1379
+ setResult(quizResult);
1380
+ onComplete?.(quizResult);
1381
+ } else if (currentQuestionIndex >= newTotalQuestions) {
1382
+ setCurrentQuestionIndex(newTotalQuestions - 1);
1383
+ }
1384
+ setShowSkipModal(false);
1385
+ setSkipComment("");
1386
+ setSelectedSkipReason(null);
1387
+ } catch (err) {
1388
+ console.error("Failed to skip question:", err);
1389
+ } finally {
1390
+ setIsSkipping(false);
1391
+ }
1392
+ }, [currentQuestion, apiClient, attempt, quiz, childId, parentId, lessonId, courseId, assignLessonId, skippedQuestionIds, extraQuestions, currentQuestionIndex, elapsedSeconds, onComplete]);
1393
+ const handleReportQuestion = (0, import_react3.useCallback)(async (comment) => {
1394
+ if (!currentQuestion || !apiClient.current || !attempt || !comment.trim()) return;
1395
+ setIsReporting(true);
1396
+ try {
1397
+ await apiClient.current.reportQuestion({
1398
+ questionId: currentQuestion.id,
1399
+ questionContent: currentQuestion,
1400
+ reportComment: comment.trim(),
1401
+ quizId: quiz?.id,
1402
+ attemptId: attempt.id,
1403
+ childId,
1404
+ parentId,
1405
+ lessonId,
1406
+ courseId,
1407
+ assignLessonId
1408
+ });
1409
+ setShowReportModal(false);
1410
+ setReportComment("");
1411
+ } catch (err) {
1412
+ console.error("Failed to report question:", err);
1413
+ } finally {
1414
+ setIsReporting(false);
1415
+ }
1416
+ }, [currentQuestion, apiClient, attempt, quiz, childId, parentId, lessonId, courseId, assignLessonId]);
699
1417
  if (isLoading) {
700
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.loading, children: "Loading quiz..." }) });
1418
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.loading, children: "Loading quiz..." }) });
701
1419
  }
702
1420
  if (error) {
703
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.error, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
1421
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.error, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { children: [
704
1422
  "Error: ",
705
1423
  error
706
1424
  ] }) }) });
707
1425
  }
708
1426
  if (isCompleted && result) {
709
- const percentage = Math.round(result.correctAnswers / result.totalQuestions * 100);
1427
+ const percentage = result.totalQuestions > 0 ? Math.round(result.correctAnswers / result.totalQuestions * 100) : 0;
710
1428
  const getScoreTheme = (pct) => {
711
1429
  if (pct >= 80) {
712
1430
  return {
@@ -761,7 +1479,7 @@ function QuizPlayer({
761
1479
  rotation: Math.random() * 360,
762
1480
  size: 6 + Math.random() * 8
763
1481
  }));
764
- const StarIcon = ({ filled, delay }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1482
+ const StarIcon = ({ filled, delay }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
765
1483
  "svg",
766
1484
  {
767
1485
  width: "36",
@@ -772,7 +1490,7 @@ function QuizPlayer({
772
1490
  animationDelay: `${delay}s`,
773
1491
  opacity: 0
774
1492
  },
775
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1493
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
776
1494
  "path",
777
1495
  {
778
1496
  d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
@@ -800,7 +1518,7 @@ function QuizPlayer({
800
1518
  }
801
1519
  };
802
1520
  const eyeExpr = getEyeExpression();
803
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1521
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
804
1522
  "svg",
805
1523
  {
806
1524
  width: "120",
@@ -810,35 +1528,35 @@ function QuizPlayer({
810
1528
  animation: mood === "celebrating" ? "bounce 0.6s ease-in-out infinite" : "gentleBob 2s ease-in-out infinite"
811
1529
  },
812
1530
  children: [
813
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "50", cy: "60", rx: "35", ry: "30", fill: "#8b5cf6" }),
814
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "50", cy: "65", rx: "25", ry: "20", fill: "#c4b5fd" }),
815
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "50", cy: "35", r: "28", fill: "#a78bfa" }),
816
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "28,15 35,30 22,28", fill: "#7c3aed" }),
817
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "72,15 65,30 78,28", fill: "#7c3aed" }),
818
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "38", cy: "38", rx: "10", ry: "12", fill: "white" }),
819
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "62", cy: "38", rx: "10", ry: "12", fill: "white" }),
820
- eyeExpr.leftEye ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: "38", y: "44", textAnchor: "middle", fontSize: "16", fill: "#1f2937", children: eyeExpr.leftEye }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "38", cy: eyeExpr.pupilY, r: "5", fill: "#1f2937" }),
821
- eyeExpr.rightEye ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { x: "62", y: "44", textAnchor: "middle", fontSize: "16", fill: "#1f2937", children: eyeExpr.rightEye }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "62", cy: eyeExpr.pupilY, r: "5", fill: "#1f2937" }),
822
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "50,45 45,52 55,52", fill: "#fbbf24" }),
823
- (mood === "celebrating" || mood === "happy") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
824
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "28", cy: "45", rx: "5", ry: "3", fill: "#fda4af", opacity: "0.6" }),
825
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "72", cy: "45", rx: "5", ry: "3", fill: "#fda4af", opacity: "0.6" })
1531
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "50", cy: "60", rx: "35", ry: "30", fill: "#8b5cf6" }),
1532
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "50", cy: "65", rx: "25", ry: "20", fill: "#c4b5fd" }),
1533
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "50", cy: "35", r: "28", fill: "#a78bfa" }),
1534
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "28,15 35,30 22,28", fill: "#7c3aed" }),
1535
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "72,15 65,30 78,28", fill: "#7c3aed" }),
1536
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "38", cy: "38", rx: "10", ry: "12", fill: "white" }),
1537
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "62", cy: "38", rx: "10", ry: "12", fill: "white" }),
1538
+ eyeExpr.leftEye ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: "38", y: "44", textAnchor: "middle", fontSize: "16", fill: "#1f2937", children: eyeExpr.leftEye }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "38", cy: eyeExpr.pupilY, r: "5", fill: "#1f2937" }),
1539
+ eyeExpr.rightEye ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: "62", y: "44", textAnchor: "middle", fontSize: "16", fill: "#1f2937", children: eyeExpr.rightEye }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "62", cy: eyeExpr.pupilY, r: "5", fill: "#1f2937" }),
1540
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "50,45 45,52 55,52", fill: "#fbbf24" }),
1541
+ (mood === "celebrating" || mood === "happy") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1542
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "28", cy: "45", rx: "5", ry: "3", fill: "#fda4af", opacity: "0.6" }),
1543
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "72", cy: "45", rx: "5", ry: "3", fill: "#fda4af", opacity: "0.6" })
826
1544
  ] }),
827
- mood === "celebrating" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
828
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "18", cy: "55", rx: "8", ry: "15", fill: "#7c3aed", transform: "rotate(-30 18 55)" }),
829
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "82", cy: "55", rx: "8", ry: "15", fill: "#7c3aed", transform: "rotate(30 82 55)" })
830
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
831
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "20", cy: "60", rx: "8", ry: "15", fill: "#7c3aed" }),
832
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "80", cy: "60", rx: "8", ry: "15", fill: "#7c3aed" })
1545
+ mood === "celebrating" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1546
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "18", cy: "55", rx: "8", ry: "15", fill: "#7c3aed", transform: "rotate(-30 18 55)" }),
1547
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "82", cy: "55", rx: "8", ry: "15", fill: "#7c3aed", transform: "rotate(30 82 55)" })
1548
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1549
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "20", cy: "60", rx: "8", ry: "15", fill: "#7c3aed" }),
1550
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "80", cy: "60", rx: "8", ry: "15", fill: "#7c3aed" })
833
1551
  ] }),
834
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "40", cy: "88", rx: "8", ry: "4", fill: "#fbbf24" }),
835
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "60", cy: "88", rx: "8", ry: "4", fill: "#fbbf24" })
1552
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "40", cy: "88", rx: "8", ry: "4", fill: "#fbbf24" }),
1553
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "60", cy: "88", rx: "8", ry: "4", fill: "#fbbf24" })
836
1554
  ]
837
1555
  }
838
1556
  );
839
1557
  };
840
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, style: defaultStyles.container, children: [
841
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
1558
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: defaultStyles.container, children: [
1559
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: `
842
1560
  @keyframes confettiFall {
843
1561
  0% {
844
1562
  transform: translateY(-10px) rotate(0deg);
@@ -902,8 +1620,8 @@ function QuizPlayer({
902
1620
  }
903
1621
  }
904
1622
  ` }),
905
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.results, children: [
906
- percentage >= 60 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.confettiContainer, children: confettiPieces.map((piece) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1623
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.results, children: [
1624
+ percentage >= 60 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.confettiContainer, children: confettiPieces.map((piece) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
907
1625
  "div",
908
1626
  {
909
1627
  style: {
@@ -920,15 +1638,15 @@ function QuizPlayer({
920
1638
  },
921
1639
  piece.id
922
1640
  )) }),
923
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...defaultStyles.resultsBackground, background: theme.bgGradient } }),
924
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.resultsContent, children: [
925
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.resultStars, children: [
926
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StarIcon, { filled: theme.stars >= 1, delay: 0.3 }),
927
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StarIcon, { filled: theme.stars >= 2, delay: 0.5 }),
928
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StarIcon, { filled: theme.stars >= 3, delay: 0.7 })
1641
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { ...defaultStyles.resultsBackground, background: theme.bgGradient } }),
1642
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.resultsContent, children: [
1643
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.resultStars, children: [
1644
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StarIcon, { filled: theme.stars >= 1, delay: 0.3 }),
1645
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StarIcon, { filled: theme.stars >= 2, delay: 0.5 }),
1646
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StarIcon, { filled: theme.stars >= 3, delay: 0.7 })
929
1647
  ] }),
930
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MascotOwl, { mood: theme.mascotMood }) }),
931
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1648
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { marginBottom: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MascotOwl, { mood: theme.mascotMood }) }),
1649
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
932
1650
  "div",
933
1651
  {
934
1652
  style: {
@@ -941,7 +1659,7 @@ function QuizPlayer({
941
1659
  opacity: 0,
942
1660
  border: `3px solid ${theme.badgeColor}`
943
1661
  },
944
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1662
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
945
1663
  "span",
946
1664
  {
947
1665
  style: {
@@ -955,7 +1673,7 @@ function QuizPlayer({
955
1673
  )
956
1674
  }
957
1675
  ),
958
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1676
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
959
1677
  "div",
960
1678
  {
961
1679
  style: {
@@ -963,7 +1681,7 @@ function QuizPlayer({
963
1681
  opacity: 0
964
1682
  },
965
1683
  children: [
966
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1684
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
967
1685
  "div",
968
1686
  {
969
1687
  style: {
@@ -980,7 +1698,7 @@ function QuizPlayer({
980
1698
  ]
981
1699
  }
982
1700
  ),
983
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1701
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
984
1702
  "div",
985
1703
  {
986
1704
  style: {
@@ -995,7 +1713,7 @@ function QuizPlayer({
995
1713
  ]
996
1714
  }
997
1715
  ),
998
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...defaultStyles.resultDetails, marginTop: "8px" }, children: [
1716
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { ...defaultStyles.resultDetails, marginTop: "8px" }, children: [
999
1717
  "Time: ",
1000
1718
  formatTime(result.timeSpentSeconds)
1001
1719
  ] })
@@ -1004,16 +1722,16 @@ function QuizPlayer({
1004
1722
  ] });
1005
1723
  }
1006
1724
  if (quiz && showIntro) {
1007
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.intro, children: [
1008
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.introTitle, children: quiz.title }),
1009
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.introSubtitle, children: "Ready to test your knowledge?" }),
1010
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.introQuestionCount, children: [
1725
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.intro, children: [
1726
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.introTitle, children: quiz.title }),
1727
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.introSubtitle, children: "Ready to test your knowledge?" }),
1728
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.introQuestionCount, children: [
1011
1729
  quiz.questions.length,
1012
1730
  " question",
1013
1731
  quiz.questions.length !== 1 ? "s" : "",
1014
1732
  " to answer"
1015
1733
  ] }),
1016
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1734
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1017
1735
  "button",
1018
1736
  {
1019
1737
  style: defaultStyles.startButton,
@@ -1033,7 +1751,7 @@ function QuizPlayer({
1033
1751
  ] }) });
1034
1752
  }
1035
1753
  if (!quiz || !currentQuestion) {
1036
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.error, children: "No quiz data available" }) });
1754
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: defaultStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.error, children: "No quiz data available" }) });
1037
1755
  }
1038
1756
  const selectedAnswer = answers.get(currentQuestion.id);
1039
1757
  const isLastQuestion = currentQuestionIndex === totalQuestions - 1;
@@ -1041,187 +1759,538 @@ function QuizPlayer({
1041
1759
  const remainingSlots = maxQuestions - totalQuestions;
1042
1760
  const questionsToAdd = Math.min(5, remainingSlots);
1043
1761
  const canAddMore = onGenerateMoreQuestions && remainingSlots > 0;
1044
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, style: defaultStyles.container, children: [
1045
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.header, children: [
1046
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.title, children: quiz.title }),
1047
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.progress, children: [
1762
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: defaultStyles.container, children: [
1763
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.header, children: [
1764
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.title, children: quiz.title }),
1765
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.progress, children: [
1048
1766
  "Question ",
1049
1767
  currentQuestionIndex + 1,
1050
1768
  " of ",
1051
1769
  totalQuestions
1052
1770
  ] }),
1053
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
1771
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.progressBar, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { ...defaultStyles.progressFill, width: `${progressPercent}%` } }) })
1054
1772
  ] }),
1055
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.question, children: [
1056
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.questionText, children: currentQuestion.question }),
1057
- (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
1058
- const isSelected = selectedAnswer === option;
1059
- const isCorrectOption = currentQuestion.correctAnswer === option;
1060
- let optionStyle = { ...defaultStyles.option };
1061
- if (showFeedback) {
1062
- if (isCorrectOption) {
1063
- optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
1064
- } else if (isSelected && !isCorrectOption) {
1065
- optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
1066
- }
1067
- } else if (isSelected) {
1068
- optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
1069
- }
1070
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1071
- "div",
1072
- {
1073
- style: {
1074
- ...optionStyle,
1075
- cursor: showFeedback ? "default" : "pointer"
1076
- },
1077
- onClick: () => !showFeedback && handleAnswerChange(option),
1078
- children: option
1079
- },
1080
- idx
1081
- );
1082
- }) }),
1083
- currentQuestion.type === "multiple" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
1084
- const selected = Array.isArray(selectedAnswer) && selectedAnswer.includes(option);
1085
- const correctAnswers = Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer : currentQuestion.correctAnswer ? [currentQuestion.correctAnswer] : [];
1086
- const isCorrectOption = correctAnswers.includes(option);
1087
- let optionStyle = { ...defaultStyles.option };
1088
- if (showFeedback) {
1089
- if (isCorrectOption) {
1090
- optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
1091
- } else if (selected && !isCorrectOption) {
1092
- optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
1093
- }
1094
- } else if (selected) {
1095
- optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
1096
- }
1097
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1098
- "div",
1099
- {
1100
- style: {
1101
- ...optionStyle,
1102
- cursor: showFeedback ? "default" : "pointer"
1103
- },
1104
- onClick: () => {
1105
- if (showFeedback) return;
1106
- const current = Array.isArray(selectedAnswer) ? selectedAnswer : [];
1107
- if (selected) {
1108
- handleAnswerChange(current.filter((o) => o !== option));
1109
- } else {
1110
- handleAnswerChange([...current, option]);
1773
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.mainLayout, children: [
1774
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.quizContent, children: [
1775
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { ...defaultStyles.question, position: "relative", paddingBottom: "40px" }, children: [
1776
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.questionText, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TextToSpeech, { text: currentQuestion.question, inline: true, size: "md" }) }),
1777
+ isExtraQuestion && !showFeedback && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1778
+ "button",
1779
+ {
1780
+ onClick: () => setShowSkipModal(true),
1781
+ title: "Skip question",
1782
+ style: {
1783
+ position: "absolute",
1784
+ bottom: "8px",
1785
+ left: "0",
1786
+ background: "transparent",
1787
+ border: "none",
1788
+ cursor: "pointer",
1789
+ padding: "6px 10px",
1790
+ borderRadius: "6px",
1791
+ color: "#9ca3af",
1792
+ display: "flex",
1793
+ alignItems: "center",
1794
+ justifyContent: "center",
1795
+ gap: "4px",
1796
+ fontSize: "12px",
1797
+ opacity: 0.6,
1798
+ transition: "opacity 0.2s, color 0.2s"
1799
+ },
1800
+ onMouseEnter: (e) => {
1801
+ e.currentTarget.style.opacity = "1";
1802
+ e.currentTarget.style.color = "#6b7280";
1803
+ },
1804
+ onMouseLeave: (e) => {
1805
+ e.currentTarget.style.opacity = "0.6";
1806
+ e.currentTarget.style.color = "#9ca3af";
1807
+ },
1808
+ "data-testid": "button-skip-question",
1809
+ children: [
1810
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1811
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "5 4 15 12 5 20 5 4" }),
1812
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "19", y1: "5", x2: "19", y2: "19" })
1813
+ ] }),
1814
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Skip" })
1815
+ ]
1816
+ }
1817
+ ),
1818
+ !isExtraQuestion && !showFeedback && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1819
+ "button",
1820
+ {
1821
+ onClick: () => setShowReportModal(true),
1822
+ title: "Report an issue with this question",
1823
+ style: {
1824
+ position: "absolute",
1825
+ bottom: "8px",
1826
+ left: "0",
1827
+ background: "transparent",
1828
+ border: "none",
1829
+ cursor: "pointer",
1830
+ padding: "6px 10px",
1831
+ borderRadius: "6px",
1832
+ color: "#9ca3af",
1833
+ display: "flex",
1834
+ alignItems: "center",
1835
+ justifyContent: "center",
1836
+ gap: "4px",
1837
+ fontSize: "12px",
1838
+ opacity: 0.6,
1839
+ transition: "opacity 0.2s, color 0.2s"
1840
+ },
1841
+ onMouseEnter: (e) => {
1842
+ e.currentTarget.style.opacity = "1";
1843
+ e.currentTarget.style.color = "#ef4444";
1844
+ },
1845
+ onMouseLeave: (e) => {
1846
+ e.currentTarget.style.opacity = "0.6";
1847
+ e.currentTarget.style.color = "#9ca3af";
1848
+ },
1849
+ "data-testid": "button-report-question",
1850
+ children: [
1851
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1852
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" }),
1853
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "4", y1: "22", x2: "4", y2: "15" })
1854
+ ] }),
1855
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Report" })
1856
+ ]
1857
+ }
1858
+ ),
1859
+ (currentQuestion.type === "single" || currentQuestion.type === "true-false") && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
1860
+ const isSelected = selectedAnswer === option;
1861
+ const isCorrectOption = currentQuestion.correctAnswer === option;
1862
+ let optionStyle = { ...defaultStyles.option };
1863
+ if (showFeedback) {
1864
+ if (isCorrectOption) {
1865
+ optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
1866
+ } else if (isSelected && !isCorrectOption) {
1867
+ optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
1868
+ }
1869
+ } else if (isSelected) {
1870
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
1871
+ }
1872
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1873
+ "div",
1874
+ {
1875
+ style: {
1876
+ ...optionStyle,
1877
+ cursor: showFeedback ? "default" : "pointer",
1878
+ display: "flex",
1879
+ alignItems: "center",
1880
+ gap: "8px"
1881
+ },
1882
+ onClick: () => !showFeedback && handleAnswerChange(option),
1883
+ children: [
1884
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TextToSpeech, { text: option, size: "sm" }) }),
1885
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 }, children: option })
1886
+ ]
1887
+ },
1888
+ idx
1889
+ );
1890
+ }) }),
1891
+ currentQuestion.type === "multiple" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.options, children: currentQuestion.options?.map((option, idx) => {
1892
+ const selected = Array.isArray(selectedAnswer) && selectedAnswer.includes(option);
1893
+ const correctAnswers = Array.isArray(currentQuestion.correctAnswer) ? currentQuestion.correctAnswer : currentQuestion.correctAnswer ? [currentQuestion.correctAnswer] : [];
1894
+ const isCorrectOption = correctAnswers.includes(option);
1895
+ let optionStyle = { ...defaultStyles.option };
1896
+ if (showFeedback) {
1897
+ if (isCorrectOption) {
1898
+ optionStyle = { ...optionStyle, ...defaultStyles.optionCorrect };
1899
+ } else if (selected && !isCorrectOption) {
1900
+ optionStyle = { ...optionStyle, ...defaultStyles.optionIncorrect };
1111
1901
  }
1902
+ } else if (selected) {
1903
+ optionStyle = { ...optionStyle, ...defaultStyles.optionSelected };
1904
+ }
1905
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1906
+ "div",
1907
+ {
1908
+ style: {
1909
+ ...optionStyle,
1910
+ cursor: showFeedback ? "default" : "pointer",
1911
+ display: "flex",
1912
+ alignItems: "center",
1913
+ gap: "8px"
1914
+ },
1915
+ onClick: () => {
1916
+ if (showFeedback) return;
1917
+ const current = Array.isArray(selectedAnswer) ? selectedAnswer : [];
1918
+ if (selected) {
1919
+ handleAnswerChange(current.filter((o) => o !== option));
1920
+ } else {
1921
+ handleAnswerChange([...current, option]);
1922
+ }
1923
+ },
1924
+ children: [
1925
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TextToSpeech, { text: option, size: "sm" }) }),
1926
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 }, children: option })
1927
+ ]
1928
+ },
1929
+ idx
1930
+ );
1931
+ }) }),
1932
+ (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1933
+ "textarea",
1934
+ {
1935
+ style: { ...defaultStyles.input, minHeight: currentQuestion.type === "essay" ? "150px" : "60px" },
1936
+ value: selectedAnswer || "",
1937
+ onChange: (e) => handleAnswerChange(e.target.value),
1938
+ placeholder: "Type your answer here...",
1939
+ disabled: showFeedback
1940
+ }
1941
+ ),
1942
+ currentQuestion.type === "fill" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1943
+ "input",
1944
+ {
1945
+ style: defaultStyles.input,
1946
+ value: (Array.isArray(selectedAnswer) ? selectedAnswer[idx] : "") || "",
1947
+ onChange: (e) => {
1948
+ const current = Array.isArray(selectedAnswer) ? [...selectedAnswer] : [];
1949
+ current[idx] = e.target.value;
1950
+ handleAnswerChange(current);
1951
+ },
1952
+ placeholder: `Blank ${idx + 1}`,
1953
+ disabled: showFeedback
1112
1954
  },
1113
- children: option
1114
- },
1115
- idx
1116
- );
1117
- }) }),
1118
- (currentQuestion.type === "free" || currentQuestion.type === "essay") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1119
- "textarea",
1120
- {
1121
- style: { ...defaultStyles.input, minHeight: currentQuestion.type === "essay" ? "150px" : "60px" },
1122
- value: selectedAnswer || "",
1123
- onChange: (e) => handleAnswerChange(e.target.value),
1124
- placeholder: "Type your answer here...",
1125
- disabled: showFeedback
1126
- }
1127
- ),
1128
- currentQuestion.type === "fill" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.options, children: currentQuestion.blanks?.map((_, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1129
- "input",
1130
- {
1131
- style: defaultStyles.input,
1132
- value: (Array.isArray(selectedAnswer) ? selectedAnswer[idx] : "") || "",
1133
- onChange: (e) => {
1134
- const current = Array.isArray(selectedAnswer) ? [...selectedAnswer] : [];
1135
- current[idx] = e.target.value;
1136
- handleAnswerChange(current);
1137
- },
1138
- placeholder: `Blank ${idx + 1}`,
1139
- disabled: showFeedback
1140
- },
1141
- idx
1142
- )) }),
1143
- showFeedback && currentAnswerDetail && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
1144
- ...defaultStyles.feedback,
1145
- ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackCorrect : defaultStyles.feedbackIncorrect
1146
- }, children: [
1147
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
1148
- ...defaultStyles.feedbackTitle,
1149
- ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackTitleCorrect : defaultStyles.feedbackTitleIncorrect
1150
- }, children: currentAnswerDetail.isCorrect ? "\u2713 Correct!" : "\u2717 Incorrect" }),
1151
- currentQuestion.explanation && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: defaultStyles.feedbackExplanation, children: currentQuestion.explanation })
1152
- ] })
1153
- ] }),
1154
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: defaultStyles.buttonsColumn, children: [
1155
- showFeedback && isLastQuestion && canAddMore && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1156
- "button",
1955
+ idx
1956
+ )) }),
1957
+ showFeedback && currentAnswerDetail && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
1958
+ ...defaultStyles.feedback,
1959
+ ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackCorrect : defaultStyles.feedbackIncorrect
1960
+ }, children: [
1961
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
1962
+ ...defaultStyles.feedbackTitle,
1963
+ ...currentAnswerDetail.isCorrect ? defaultStyles.feedbackTitleCorrect : defaultStyles.feedbackTitleIncorrect
1964
+ }, children: currentAnswerDetail.isCorrect ? "\u2713 Correct!" : "\u2717 Incorrect" }),
1965
+ currentQuestion.explanation && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.feedbackExplanation, children: currentQuestion.explanation })
1966
+ ] })
1967
+ ] }),
1968
+ showSkipModal && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
1969
+ position: "fixed",
1970
+ top: 0,
1971
+ left: 0,
1972
+ right: 0,
1973
+ bottom: 0,
1974
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
1975
+ display: "flex",
1976
+ alignItems: "center",
1977
+ justifyContent: "center",
1978
+ zIndex: 1e3
1979
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
1980
+ backgroundColor: "#ffffff",
1981
+ borderRadius: "12px",
1982
+ padding: "24px",
1983
+ maxWidth: "400px",
1984
+ width: "90%",
1985
+ boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
1986
+ }, children: [
1987
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Skip Question" }),
1988
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "Help us improve by telling us why you're skipping this question." }),
1989
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "8px", marginBottom: "16px" }, children: [
1990
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1991
+ "button",
1992
+ {
1993
+ onClick: () => setSelectedSkipReason("question_issue"),
1994
+ disabled: isSkipping,
1995
+ style: {
1996
+ padding: "12px 16px",
1997
+ borderRadius: "8px",
1998
+ border: selectedSkipReason === "question_issue" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
1999
+ backgroundColor: selectedSkipReason === "question_issue" ? "#f5f3ff" : "#f9fafb",
2000
+ cursor: isSkipping ? "not-allowed" : "pointer",
2001
+ fontSize: "14px",
2002
+ fontWeight: "500",
2003
+ color: "#374151",
2004
+ textAlign: "left",
2005
+ opacity: isSkipping ? 0.6 : 1
2006
+ },
2007
+ "data-testid": "button-skip-reason-issue",
2008
+ children: "Question has an issue"
2009
+ }
2010
+ ),
2011
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2012
+ "button",
2013
+ {
2014
+ onClick: () => setSelectedSkipReason("dont_know"),
2015
+ disabled: isSkipping,
2016
+ style: {
2017
+ padding: "12px 16px",
2018
+ borderRadius: "8px",
2019
+ border: selectedSkipReason === "dont_know" ? "2px solid #8b5cf6" : "1px solid #e5e7eb",
2020
+ backgroundColor: selectedSkipReason === "dont_know" ? "#f5f3ff" : "#f9fafb",
2021
+ cursor: isSkipping ? "not-allowed" : "pointer",
2022
+ fontSize: "14px",
2023
+ fontWeight: "500",
2024
+ color: "#374151",
2025
+ textAlign: "left",
2026
+ opacity: isSkipping ? 0.6 : 1
2027
+ },
2028
+ "data-testid": "button-skip-reason-dont-know",
2029
+ children: "I don't know the answer"
2030
+ }
2031
+ )
2032
+ ] }),
2033
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "16px" }, children: [
2034
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { style: { display: "block", fontSize: "13px", fontWeight: "500", color: "#374151", marginBottom: "6px" }, children: "Additional details (optional)" }),
2035
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2036
+ "textarea",
2037
+ {
2038
+ value: skipComment,
2039
+ onChange: (e) => setSkipComment(e.target.value.slice(0, 200)),
2040
+ placeholder: "Tell us more about the issue...",
2041
+ disabled: isSkipping,
2042
+ style: {
2043
+ width: "100%",
2044
+ minHeight: "80px",
2045
+ padding: "10px 12px",
2046
+ borderRadius: "8px",
2047
+ border: "1px solid #e5e7eb",
2048
+ fontSize: "14px",
2049
+ resize: "vertical",
2050
+ fontFamily: "inherit",
2051
+ boxSizing: "border-box"
2052
+ },
2053
+ "data-testid": "input-skip-comment"
2054
+ }
2055
+ ),
2056
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2057
+ skipComment.length,
2058
+ "/200"
2059
+ ] })
2060
+ ] }),
2061
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: "10px" }, children: [
2062
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2063
+ "button",
2064
+ {
2065
+ onClick: () => {
2066
+ setShowSkipModal(false);
2067
+ setSkipComment("");
2068
+ setSelectedSkipReason(null);
2069
+ },
2070
+ style: {
2071
+ flex: 1,
2072
+ padding: "10px 16px",
2073
+ borderRadius: "8px",
2074
+ border: "1px solid #e5e7eb",
2075
+ backgroundColor: "#ffffff",
2076
+ cursor: "pointer",
2077
+ fontSize: "14px",
2078
+ fontWeight: "500",
2079
+ color: "#6b7280"
2080
+ },
2081
+ "data-testid": "button-skip-cancel",
2082
+ children: "Cancel"
2083
+ }
2084
+ ),
2085
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2086
+ "button",
2087
+ {
2088
+ onClick: () => selectedSkipReason && handleSkipQuestion(selectedSkipReason, skipComment),
2089
+ disabled: isSkipping || !selectedSkipReason,
2090
+ style: {
2091
+ flex: 1,
2092
+ padding: "10px 16px",
2093
+ borderRadius: "8px",
2094
+ border: "none",
2095
+ backgroundColor: selectedSkipReason ? "#8b5cf6" : "#d1d5db",
2096
+ cursor: isSkipping || !selectedSkipReason ? "not-allowed" : "pointer",
2097
+ fontSize: "14px",
2098
+ fontWeight: "500",
2099
+ color: "#ffffff",
2100
+ opacity: isSkipping ? 0.6 : 1
2101
+ },
2102
+ "data-testid": "button-skip-submit",
2103
+ children: isSkipping ? "Skipping..." : "Skip Question"
2104
+ }
2105
+ )
2106
+ ] })
2107
+ ] }) }),
2108
+ showReportModal && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
2109
+ position: "fixed",
2110
+ top: 0,
2111
+ left: 0,
2112
+ right: 0,
2113
+ bottom: 0,
2114
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
2115
+ display: "flex",
2116
+ alignItems: "center",
2117
+ justifyContent: "center",
2118
+ zIndex: 1e3
2119
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
2120
+ backgroundColor: "#ffffff",
2121
+ borderRadius: "12px",
2122
+ padding: "24px",
2123
+ maxWidth: "400px",
2124
+ width: "90%",
2125
+ boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)"
2126
+ }, children: [
2127
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { style: { margin: "0 0 8px 0", fontSize: "18px", fontWeight: "600", color: "#1f2937" }, children: "Report Question" }),
2128
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6b7280" }, children: "What's wrong with this question?" }),
2129
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "16px" }, children: [
2130
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2131
+ "textarea",
2132
+ {
2133
+ value: reportComment,
2134
+ onChange: (e) => setReportComment(e.target.value.slice(0, 300)),
2135
+ placeholder: "Describe the issue with this question...",
2136
+ disabled: isReporting,
2137
+ style: {
2138
+ width: "100%",
2139
+ minHeight: "120px",
2140
+ padding: "10px 12px",
2141
+ borderRadius: "8px",
2142
+ border: "1px solid #e5e7eb",
2143
+ fontSize: "14px",
2144
+ resize: "vertical",
2145
+ fontFamily: "inherit",
2146
+ boxSizing: "border-box"
2147
+ },
2148
+ "data-testid": "input-report-comment"
2149
+ }
2150
+ ),
2151
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontSize: "12px", color: "#9ca3af", marginTop: "4px", textAlign: "right" }, children: [
2152
+ reportComment.length,
2153
+ "/300"
2154
+ ] })
2155
+ ] }),
2156
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: "10px" }, children: [
2157
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2158
+ "button",
2159
+ {
2160
+ onClick: () => {
2161
+ setShowReportModal(false);
2162
+ setReportComment("");
2163
+ },
2164
+ style: {
2165
+ flex: 1,
2166
+ padding: "10px 16px",
2167
+ borderRadius: "8px",
2168
+ border: "1px solid #e5e7eb",
2169
+ backgroundColor: "#ffffff",
2170
+ cursor: "pointer",
2171
+ fontSize: "14px",
2172
+ fontWeight: "500",
2173
+ color: "#6b7280"
2174
+ },
2175
+ "data-testid": "button-report-cancel",
2176
+ children: "Cancel"
2177
+ }
2178
+ ),
2179
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2180
+ "button",
2181
+ {
2182
+ onClick: () => handleReportQuestion(reportComment),
2183
+ disabled: isReporting || !reportComment.trim(),
2184
+ style: {
2185
+ flex: 1,
2186
+ padding: "10px 16px",
2187
+ borderRadius: "8px",
2188
+ border: "none",
2189
+ backgroundColor: reportComment.trim() ? "#ef4444" : "#d1d5db",
2190
+ cursor: isReporting || !reportComment.trim() ? "not-allowed" : "pointer",
2191
+ fontSize: "14px",
2192
+ fontWeight: "500",
2193
+ color: "#ffffff",
2194
+ opacity: isReporting ? 0.6 : 1
2195
+ },
2196
+ "data-testid": "button-report-submit",
2197
+ children: isReporting ? "Reporting..." : "Report"
2198
+ }
2199
+ )
2200
+ ] })
2201
+ ] }) }),
2202
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.buttonsColumn, children: [
2203
+ showFeedback && isLastQuestion && canAddMore && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2204
+ "button",
2205
+ {
2206
+ style: {
2207
+ ...defaultStyles.buttonAddMore,
2208
+ ...isGeneratingExtra ? defaultStyles.buttonAddMoreDisabled : {}
2209
+ },
2210
+ onClick: handleAddMoreQuestions,
2211
+ disabled: isGeneratingExtra,
2212
+ "data-testid": "button-add-more-questions",
2213
+ children: isGeneratingExtra ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2214
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: 16, color: "#9ca3af" }),
2215
+ "Generating Questions..."
2216
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2217
+ "+ Add ",
2218
+ questionsToAdd,
2219
+ " More Question",
2220
+ questionsToAdd !== 1 ? "s" : ""
2221
+ ] })
2222
+ }
2223
+ ),
2224
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { ...defaultStyles.buttons, justifyContent: "flex-end" }, children: showFeedback ? (
2225
+ // After viewing feedback
2226
+ isLastQuestion ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2227
+ "button",
2228
+ {
2229
+ style: {
2230
+ ...defaultStyles.button,
2231
+ ...isSubmitting || isGeneratingExtra ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2232
+ },
2233
+ onClick: handleSubmit,
2234
+ disabled: isSubmitting || isGeneratingExtra,
2235
+ "data-testid": "button-submit-quiz",
2236
+ children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
2237
+ }
2238
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2239
+ "button",
2240
+ {
2241
+ style: {
2242
+ ...defaultStyles.button,
2243
+ ...defaultStyles.buttonPrimary
2244
+ },
2245
+ onClick: handleContinue,
2246
+ "data-testid": "button-continue",
2247
+ children: "Continue"
2248
+ }
2249
+ )
2250
+ ) : (
2251
+ // Before checking answer
2252
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2253
+ "button",
2254
+ {
2255
+ style: {
2256
+ ...defaultStyles.button,
2257
+ ...isNavigating || selectedAnswer === void 0 ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
2258
+ },
2259
+ onClick: handleCheckAnswer,
2260
+ disabled: isNavigating || selectedAnswer === void 0,
2261
+ "data-testid": "button-check-answer",
2262
+ children: isNavigating ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Check Answer"
2263
+ }
2264
+ )
2265
+ ) })
2266
+ ] })
2267
+ ] }),
2268
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.chatPanel, children: apiClient.current && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2269
+ QuestionChatPanel,
1157
2270
  {
1158
- style: {
1159
- ...defaultStyles.buttonAddMore,
1160
- ...isGeneratingExtra ? defaultStyles.buttonAddMoreDisabled : {}
2271
+ apiClient: apiClient.current,
2272
+ question: {
2273
+ id: currentQuestion.id,
2274
+ question: currentQuestion.question,
2275
+ type: currentQuestion.type,
2276
+ options: currentQuestion.options,
2277
+ correctAnswer: currentQuestion.correctAnswer,
2278
+ explanation: currentQuestion.explanation
1161
2279
  },
1162
- onClick: handleAddMoreQuestions,
1163
- disabled: isGeneratingExtra,
1164
- "data-testid": "button-add-more-questions",
1165
- children: isGeneratingExtra ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1166
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }),
1167
- "Generating Questions..."
1168
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1169
- "+ Add ",
1170
- questionsToAdd,
1171
- " More Question",
1172
- questionsToAdd !== 1 ? "s" : ""
1173
- ] })
2280
+ quizId: quiz.id,
2281
+ childId,
2282
+ parentId,
2283
+ lessonId,
2284
+ courseId
1174
2285
  }
1175
- ),
1176
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...defaultStyles.buttons, justifyContent: "flex-end" }, children: showFeedback ? (
1177
- // After viewing feedback
1178
- isLastQuestion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1179
- "button",
1180
- {
1181
- style: {
1182
- ...defaultStyles.button,
1183
- ...isSubmitting || isGeneratingExtra ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
1184
- },
1185
- onClick: handleSubmit,
1186
- disabled: isSubmitting || isGeneratingExtra,
1187
- "data-testid": "button-submit-quiz",
1188
- children: isSubmitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Submit Quiz"
1189
- }
1190
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1191
- "button",
1192
- {
1193
- style: {
1194
- ...defaultStyles.button,
1195
- ...defaultStyles.buttonPrimary
1196
- },
1197
- onClick: handleContinue,
1198
- "data-testid": "button-continue",
1199
- children: "Continue"
1200
- }
1201
- )
1202
- ) : (
1203
- // Before checking answer
1204
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1205
- "button",
1206
- {
1207
- style: {
1208
- ...defaultStyles.button,
1209
- ...isNavigating || selectedAnswer === void 0 ? defaultStyles.buttonDisabled : defaultStyles.buttonPrimary
1210
- },
1211
- onClick: handleCheckAnswer,
1212
- disabled: isNavigating || selectedAnswer === void 0,
1213
- "data-testid": "button-check-answer",
1214
- children: isNavigating ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: 16, color: "#9ca3af" }) : "Check Answer"
1215
- }
1216
- )
1217
2286
  ) })
1218
2287
  ] })
1219
2288
  ] });
1220
2289
  }
1221
2290
 
1222
2291
  // src/AttemptViewer.tsx
1223
- var import_react2 = require("react");
1224
- var import_jsx_runtime2 = require("react/jsx-runtime");
2292
+ var import_react4 = require("react");
2293
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1225
2294
  var defaultStyles2 = {
1226
2295
  container: {
1227
2296
  fontFamily: "system-ui, -apple-system, sans-serif",
@@ -1401,10 +2470,10 @@ function AttemptViewer({
1401
2470
  showExplanations = true,
1402
2471
  title
1403
2472
  }) {
1404
- const [attempt, setAttempt] = (0, import_react2.useState)(null);
1405
- const [loading, setLoading] = (0, import_react2.useState)(true);
1406
- const [error, setError] = (0, import_react2.useState)(null);
1407
- (0, import_react2.useEffect)(() => {
2473
+ const [attempt, setAttempt] = (0, import_react4.useState)(null);
2474
+ const [loading, setLoading] = (0, import_react4.useState)(true);
2475
+ const [error, setError] = (0, import_react4.useState)(null);
2476
+ (0, import_react4.useEffect)(() => {
1408
2477
  const apiClient = new QuizApiClient({
1409
2478
  baseUrl: apiBaseUrl,
1410
2479
  authToken
@@ -1437,49 +2506,49 @@ function AttemptViewer({
1437
2506
  window.location.reload();
1438
2507
  };
1439
2508
  if (loading) {
1440
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.container, className, children: [
1441
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: spinnerKeyframes }),
1442
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.loading, children: [
1443
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.spinner }),
1444
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { marginTop: "16px", color: "#6b7280" }, children: "Loading attempt..." })
2509
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.container, className, children: [
2510
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: spinnerKeyframes }),
2511
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.loading, children: [
2512
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.spinner }),
2513
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { marginTop: "16px", color: "#6b7280" }, children: "Loading attempt..." })
1445
2514
  ] })
1446
2515
  ] });
1447
2516
  }
1448
2517
  if (error || !attempt) {
1449
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.container, className, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.error, children: [
1450
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "18px", fontWeight: "500" }, children: "Failed to load attempt" }),
1451
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { marginTop: "8px", color: "#6b7280" }, children: error }),
1452
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: defaultStyles2.retryButton, onClick: handleRetry, children: "Try Again" })
2518
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.container, className, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.error, children: [
2519
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { fontSize: "18px", fontWeight: "500" }, children: "Failed to load attempt" }),
2520
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { marginTop: "8px", color: "#6b7280" }, children: error }),
2521
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { style: defaultStyles2.retryButton, onClick: handleRetry, children: "Try Again" })
1453
2522
  ] }) });
1454
2523
  }
1455
2524
  const scorePercentage = attempt.score ?? 0;
1456
2525
  const correctCount = attempt.correctAnswers ?? 0;
1457
2526
  const totalQuestions = attempt.totalQuestions;
1458
2527
  const timeSpent = attempt.timeSpentSeconds ?? 0;
1459
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.container, className, children: [
1460
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: spinnerKeyframes }),
1461
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.header, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryGrid, children: [
1462
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
1463
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
2528
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.container, className, children: [
2529
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: spinnerKeyframes }),
2530
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.header, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryGrid, children: [
2531
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
2532
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
1464
2533
  scorePercentage,
1465
2534
  "%"
1466
2535
  ] }),
1467
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Score" })
2536
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Score" })
1468
2537
  ] }),
1469
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
1470
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
2538
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
2539
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryValue, children: [
1471
2540
  correctCount,
1472
2541
  "/",
1473
2542
  totalQuestions
1474
2543
  ] }),
1475
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Correct" })
2544
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Correct" })
1476
2545
  ] }),
1477
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
1478
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryValue, children: formatTime(timeSpent) }),
1479
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Time" })
2546
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.summaryCard, children: [
2547
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.summaryValue, children: formatTime(timeSpent) }),
2548
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.summaryLabel, children: "Time" })
1480
2549
  ] })
1481
2550
  ] }) }),
1482
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.questionsList, children: attempt.answers.map((answer, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2551
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.questionsList, children: attempt.answers.map((answer, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1483
2552
  "div",
1484
2553
  {
1485
2554
  style: {
@@ -1487,12 +2556,12 @@ function AttemptViewer({
1487
2556
  ...answer.isCorrect ? defaultStyles2.questionCardCorrect : defaultStyles2.questionCardIncorrect
1488
2557
  },
1489
2558
  children: [
1490
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.questionHeader, children: [
1491
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: defaultStyles2.questionNumber, children: [
2559
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.questionHeader, children: [
2560
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: defaultStyles2.questionNumber, children: [
1492
2561
  "Question ",
1493
2562
  index + 1
1494
2563
  ] }),
1495
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2564
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1496
2565
  "span",
1497
2566
  {
1498
2567
  style: {
@@ -1503,23 +2572,23 @@ function AttemptViewer({
1503
2572
  }
1504
2573
  )
1505
2574
  ] }),
1506
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: defaultStyles2.questionText, children: answer.questionText }),
1507
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.answerSection, children: [
1508
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.answerLabel, children: "Your answer:" }),
1509
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.studentAnswer, children: formatAnswer(answer.selectedAnswer) })
2575
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: defaultStyles2.questionText, children: answer.questionText }),
2576
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.answerSection, children: [
2577
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.answerLabel, children: "Your answer:" }),
2578
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.studentAnswer, children: formatAnswer(answer.selectedAnswer) })
1510
2579
  ] }),
1511
- !answer.isCorrect && answer.correctAnswer && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.answerSection, children: [
1512
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.answerLabel, children: "Correct answer:" }),
1513
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: defaultStyles2.correctAnswer, children: formatAnswer(answer.correctAnswer) })
2580
+ !answer.isCorrect && answer.correctAnswer && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.answerSection, children: [
2581
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.answerLabel, children: "Correct answer:" }),
2582
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: defaultStyles2.correctAnswer, children: formatAnswer(answer.correctAnswer) })
1514
2583
  ] }),
1515
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.points, children: [
2584
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.points, children: [
1516
2585
  answer.pointsEarned,
1517
2586
  " / ",
1518
2587
  answer.points,
1519
2588
  " points"
1520
2589
  ] }),
1521
- showExplanations && answer.explanation && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: defaultStyles2.explanation, children: [
1522
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "Explanation:" }),
2590
+ showExplanations && answer.explanation && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: defaultStyles2.explanation, children: [
2591
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: "Explanation:" }),
1523
2592
  " ",
1524
2593
  answer.explanation
1525
2594
  ] })
@@ -1534,6 +2603,7 @@ function AttemptViewer({
1534
2603
  AttemptViewer,
1535
2604
  QuizApiClient,
1536
2605
  QuizPlayer,
2606
+ TextToSpeech,
1537
2607
  calculateScore,
1538
2608
  checkAnswer,
1539
2609
  createAnswerDetail,