@schoolio/player 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -237,6 +237,7 @@ declare class QuizApiClient {
237
237
  chatId: string | null;
238
238
  messages: ChatMessage[];
239
239
  }>;
240
+ getTextToSpeech(text: string, voice?: string): Promise<Blob>;
240
241
  }
241
242
 
242
243
  declare function checkAnswer(question: QuizQuestion, selectedAnswer: unknown): {
package/dist/index.d.ts CHANGED
@@ -237,6 +237,7 @@ declare class QuizApiClient {
237
237
  chatId: string | null;
238
238
  messages: ChatMessage[];
239
239
  }>;
240
+ getTextToSpeech(text: string, voice?: string): Promise<Blob>;
240
241
  }
241
242
 
242
243
  declare function checkAnswer(question: QuizQuestion, selectedAnswer: unknown): {
package/dist/index.js CHANGED
@@ -113,6 +113,23 @@ var QuizApiClient = class {
113
113
  `/api/external/question-chat/${questionId}/${childId}`
114
114
  );
115
115
  }
116
+ async getTextToSpeech(text, voice = "nova") {
117
+ const headers = {
118
+ "Content-Type": "application/json"
119
+ };
120
+ if (this.authToken) {
121
+ headers["Authorization"] = `Bearer ${this.authToken}`;
122
+ }
123
+ const response = await fetch(`${this.baseUrl}/api/external/tts`, {
124
+ method: "POST",
125
+ headers,
126
+ body: JSON.stringify({ text, voice })
127
+ });
128
+ if (!response.ok) {
129
+ throw new Error(`TTS request failed: HTTP ${response.status}`);
130
+ }
131
+ return response.blob();
132
+ }
116
133
  };
117
134
 
118
135
  // src/utils.ts
@@ -515,9 +532,13 @@ var panelStyles = {
515
532
  messageRow: {
516
533
  display: "flex"
517
534
  },
535
+ messageContent: {
536
+ display: "flex",
537
+ alignItems: "flex-end",
538
+ gap: "4px",
539
+ maxWidth: "85%"
540
+ },
518
541
  userMessage: {
519
- maxWidth: "85%",
520
- marginLeft: "auto",
521
542
  padding: "10px 14px",
522
543
  borderRadius: "16px 16px 4px 16px",
523
544
  backgroundColor: "#6721b0",
@@ -526,8 +547,6 @@ var panelStyles = {
526
547
  lineHeight: 1.4
527
548
  },
528
549
  assistantMessage: {
529
- maxWidth: "85%",
530
- marginRight: "auto",
531
550
  padding: "10px 14px",
532
551
  borderRadius: "16px 16px 16px 4px",
533
552
  backgroundColor: "#ffffff",
@@ -541,7 +560,8 @@ var panelStyles = {
541
560
  borderTop: "1px solid #e2e8f0",
542
561
  backgroundColor: "#ffffff",
543
562
  display: "flex",
544
- gap: "8px"
563
+ gap: "8px",
564
+ alignItems: "center"
545
565
  },
546
566
  input: {
547
567
  flex: 1,
@@ -551,13 +571,11 @@ var panelStyles = {
551
571
  fontSize: "14px",
552
572
  outline: "none"
553
573
  },
554
- sendButton: {
574
+ buttonBase: {
555
575
  width: "40px",
556
576
  height: "40px",
557
577
  borderRadius: "50%",
558
578
  border: "none",
559
- backgroundColor: "#6721b0",
560
- color: "#ffffff",
561
579
  cursor: "pointer",
562
580
  display: "flex",
563
581
  alignItems: "center",
@@ -565,10 +583,47 @@ var panelStyles = {
565
583
  fontSize: "16px",
566
584
  transition: "all 0.2s ease"
567
585
  },
586
+ sendButton: {
587
+ backgroundColor: "#6721b0",
588
+ color: "#ffffff"
589
+ },
568
590
  sendButtonDisabled: {
569
591
  backgroundColor: "#d1d5db",
570
592
  cursor: "not-allowed"
571
593
  },
594
+ micButton: {
595
+ backgroundColor: "#ffffff",
596
+ border: "1px solid #e2e8f0",
597
+ color: "#374151"
598
+ },
599
+ micButtonActive: {
600
+ backgroundColor: "#ef4444",
601
+ color: "#ffffff",
602
+ border: "none"
603
+ },
604
+ speakButton: {
605
+ width: "28px",
606
+ height: "28px",
607
+ borderRadius: "50%",
608
+ border: "none",
609
+ backgroundColor: "transparent",
610
+ color: "#9ca3af",
611
+ cursor: "pointer",
612
+ display: "flex",
613
+ alignItems: "center",
614
+ justifyContent: "center",
615
+ transition: "all 0.2s ease",
616
+ flexShrink: 0
617
+ },
618
+ speakButtonReady: {
619
+ color: "#22c55e"
620
+ },
621
+ speakButtonPlaying: {
622
+ color: "#6721b0"
623
+ },
624
+ speakButtonLoading: {
625
+ color: "#f97316"
626
+ },
572
627
  loadingDots: {
573
628
  display: "flex",
574
629
  alignItems: "center",
@@ -606,6 +661,33 @@ var STARTER_PROMPTS = [
606
661
  { id: "word_help", label: "I don't understand a word", message: "I don't understand a word in this question. Can you help?" },
607
662
  { id: "explain_again", label: "Explain this again", message: "Can you explain this question to me again in a different way?" }
608
663
  ];
664
+ var MicIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
665
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }),
666
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
667
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", x2: "12", y1: "19", y2: "22" })
668
+ ] });
669
+ var MicOffIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
670
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "2", x2: "22", y1: "2", y2: "22" }),
671
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18.89 13.23A7.12 7.12 0 0 0 19 12v-2" }),
672
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M5 10v2a7 7 0 0 0 12 5" }),
673
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33" }),
674
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12" }),
675
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", x2: "12", y1: "19", y2: "22" })
676
+ ] });
677
+ var VolumeIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
678
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
679
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
680
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
681
+ ] });
682
+ var SendIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
683
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
684
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
685
+ ] });
686
+ var HelpIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", stroke: "#6721b0", strokeWidth: "2", children: [
687
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
688
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }),
689
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
690
+ ] });
609
691
  function QuestionChatPanel({
610
692
  apiClient,
611
693
  question,
@@ -620,13 +702,121 @@ function QuestionChatPanel({
620
702
  const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
621
703
  const [chatId, setChatId] = (0, import_react2.useState)(null);
622
704
  const [hoveredButton, setHoveredButton] = (0, import_react2.useState)(null);
705
+ const [isListening, setIsListening] = (0, import_react2.useState)(false);
706
+ const [speakingIndex, setSpeakingIndex] = (0, import_react2.useState)(null);
707
+ const [audioReadyMap, setAudioReadyMap] = (0, import_react2.useState)(/* @__PURE__ */ new Map());
623
708
  const messagesContainerRef = (0, import_react2.useRef)(null);
624
709
  const messagesEndRef = (0, import_react2.useRef)(null);
710
+ const recognitionRef = (0, import_react2.useRef)(null);
711
+ const audioRef = (0, import_react2.useRef)(null);
712
+ const audioCacheRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
713
+ const isSpeechSupported = typeof window !== "undefined" && (window.SpeechRecognition || window.webkitSpeechRecognition);
625
714
  const scrollToBottom = (0, import_react2.useCallback)(() => {
626
715
  if (messagesContainerRef.current) {
627
716
  messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
628
717
  }
629
718
  }, []);
719
+ const preCacheAudio = (0, import_react2.useCallback)(async (text) => {
720
+ if (audioCacheRef.current.has(text)) {
721
+ setAudioReadyMap((prev) => new Map(prev).set(text, true));
722
+ return;
723
+ }
724
+ setAudioReadyMap((prev) => new Map(prev).set(text, false));
725
+ try {
726
+ const audioBlob = await apiClient.getTextToSpeech(text, "nova");
727
+ audioCacheRef.current.set(text, audioBlob);
728
+ setAudioReadyMap((prev) => new Map(prev).set(text, true));
729
+ } catch (error) {
730
+ console.error("Pre-cache TTS error:", error);
731
+ }
732
+ }, [apiClient]);
733
+ const startListening = (0, import_react2.useCallback)(() => {
734
+ const SpeechRecognitionAPI = window.SpeechRecognition || window.webkitSpeechRecognition;
735
+ if (!SpeechRecognitionAPI) {
736
+ console.log("Speech recognition not supported");
737
+ return;
738
+ }
739
+ const recognition = new SpeechRecognitionAPI();
740
+ recognition.continuous = true;
741
+ recognition.interimResults = true;
742
+ recognition.lang = "en-US";
743
+ recognition.onstart = () => {
744
+ console.log("Speech recognition started");
745
+ setIsListening(true);
746
+ };
747
+ recognition.onresult = (event) => {
748
+ let finalTranscript = "";
749
+ for (let i = 0; i < Object.keys(event.results).length; i++) {
750
+ const result = event.results[i];
751
+ if (result && result[0]) {
752
+ finalTranscript += result[0].transcript;
753
+ }
754
+ }
755
+ if (finalTranscript) {
756
+ setInputValue(finalTranscript);
757
+ }
758
+ };
759
+ recognition.onerror = (event) => {
760
+ console.log("Speech recognition error:", event.error);
761
+ if (event.error === "not-allowed") {
762
+ alert("Microphone access was denied. Please allow microphone access and try again.");
763
+ }
764
+ setIsListening(false);
765
+ };
766
+ recognition.onend = () => {
767
+ console.log("Speech recognition ended");
768
+ setIsListening(false);
769
+ };
770
+ recognitionRef.current = recognition;
771
+ try {
772
+ recognition.start();
773
+ } catch (e) {
774
+ console.log("Failed to start recognition:", e);
775
+ setIsListening(false);
776
+ }
777
+ }, []);
778
+ const stopListening = (0, import_react2.useCallback)(() => {
779
+ if (recognitionRef.current) {
780
+ recognitionRef.current.stop();
781
+ setIsListening(false);
782
+ }
783
+ }, []);
784
+ const speakMessage = (0, import_react2.useCallback)(async (text, index) => {
785
+ if (audioRef.current) {
786
+ audioRef.current.pause();
787
+ audioRef.current = null;
788
+ }
789
+ if (speakingIndex === index) {
790
+ setSpeakingIndex(null);
791
+ return;
792
+ }
793
+ setSpeakingIndex(index);
794
+ try {
795
+ let audioBlob;
796
+ const cachedBlob = audioCacheRef.current.get(text);
797
+ if (cachedBlob) {
798
+ audioBlob = cachedBlob;
799
+ } else {
800
+ audioBlob = await apiClient.getTextToSpeech(text, "nova");
801
+ audioCacheRef.current.set(text, audioBlob);
802
+ }
803
+ const audioUrl = URL.createObjectURL(audioBlob);
804
+ const audio = new Audio(audioUrl);
805
+ audioRef.current = audio;
806
+ audio.onended = () => {
807
+ setSpeakingIndex(null);
808
+ URL.revokeObjectURL(audioUrl);
809
+ };
810
+ audio.onerror = () => {
811
+ setSpeakingIndex(null);
812
+ URL.revokeObjectURL(audioUrl);
813
+ };
814
+ await audio.play();
815
+ } catch (error) {
816
+ console.error("TTS error:", error);
817
+ setSpeakingIndex(null);
818
+ }
819
+ }, [speakingIndex, apiClient]);
630
820
  (0, import_react2.useEffect)(() => {
631
821
  scrollToBottom();
632
822
  }, [messages, scrollToBottom]);
@@ -640,13 +830,14 @@ function QuestionChatPanel({
640
830
  if (history.chatId && history.messages.length > 0) {
641
831
  setChatId(history.chatId);
642
832
  setMessages(history.messages);
833
+ history.messages.filter((m) => m.role === "assistant").forEach((m) => preCacheAudio(m.content));
643
834
  }
644
835
  } catch (err) {
645
836
  console.error("Failed to load chat history:", err);
646
837
  }
647
838
  };
648
839
  loadHistory();
649
- }, [question.id, childId, apiClient]);
840
+ }, [question.id, childId, apiClient, preCacheAudio]);
650
841
  const initializeChat = async () => {
651
842
  if (chatId) return chatId;
652
843
  try {
@@ -668,6 +859,9 @@ function QuestionChatPanel({
668
859
  };
669
860
  const sendMessage = async (messageText) => {
670
861
  if (!messageText.trim() || isLoading) return;
862
+ if (isListening) {
863
+ stopListening();
864
+ }
671
865
  setIsLoading(true);
672
866
  const userMsg = {
673
867
  role: "user",
@@ -687,15 +881,23 @@ function QuestionChatPanel({
687
881
  questionContext: question,
688
882
  childId
689
883
  });
690
- setMessages((prev) => [...prev, response.assistantMessage]);
884
+ const assistantMessage = response.assistantMessage || {
885
+ role: "assistant",
886
+ content: "I'm here to help!",
887
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
888
+ };
889
+ setMessages((prev) => [...prev, assistantMessage]);
890
+ preCacheAudio(assistantMessage.content);
691
891
  } catch (err) {
692
892
  console.error("Failed to send message:", err);
893
+ const content = "Sorry, I'm having trouble right now. Please try again!";
693
894
  const errorMsg = {
694
895
  role: "assistant",
695
- content: "Sorry, I'm having trouble right now. Please try again!",
896
+ content,
696
897
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
697
898
  };
698
899
  setMessages((prev) => [...prev, errorMsg]);
900
+ preCacheAudio(content);
699
901
  } finally {
700
902
  setIsLoading(false);
701
903
  }
@@ -712,14 +914,14 @@ function QuestionChatPanel({
712
914
  0%, 60%, 100% { transform: translateY(0); }
713
915
  30% { transform: translateY(-4px); }
714
916
  }
917
+ @keyframes pulse {
918
+ 0%, 100% { opacity: 1; }
919
+ 50% { opacity: 0.5; }
920
+ }
715
921
  ` }),
716
922
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: panelStyles.header, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Need Help?" }) }),
717
923
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: messagesContainerRef, style: panelStyles.messagesContainer, children: messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyles.emptyState, children: [
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
- ] }) }),
924
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: panelStyles.helperIcon, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(HelpIcon, {}) }),
723
925
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "14px", fontWeight: "500", marginBottom: "8px" }, children: "Hi! I'm your question helper" }),
724
926
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: "13px", color: "#9ca3af" }, children: "Ask me if you need help understanding this question" }),
725
927
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...panelStyles.starterPrompts, marginTop: "16px" }, children: STARTER_PROMPTS.map((prompt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -746,7 +948,26 @@ function QuestionChatPanel({
746
948
  ...panelStyles.messageRow,
747
949
  justifyContent: msg.role === "user" ? "flex-end" : "flex-start"
748
950
  },
749
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: msg.role === "user" ? panelStyles.userMessage : panelStyles.assistantMessage, children: msg.content })
951
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
952
+ ...panelStyles.messageContent,
953
+ flexDirection: msg.role === "user" ? "row-reverse" : "row"
954
+ }, children: [
955
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: msg.role === "user" ? panelStyles.userMessage : panelStyles.assistantMessage, children: msg.content }),
956
+ msg.role === "assistant" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
957
+ "button",
958
+ {
959
+ style: {
960
+ ...panelStyles.speakButton,
961
+ ...speakingIndex === idx ? panelStyles.speakButtonPlaying : audioReadyMap.get(msg.content) === true ? panelStyles.speakButtonReady : audioReadyMap.get(msg.content) === false ? panelStyles.speakButtonLoading : {},
962
+ ...audioReadyMap.get(msg.content) === false ? { animation: "pulse 1.5s ease-in-out infinite" } : {}
963
+ },
964
+ onClick: () => speakMessage(msg.content, idx),
965
+ "data-testid": `button-speak-${idx}`,
966
+ title: "Listen to this message",
967
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VolumeIcon2, {})
968
+ }
969
+ )
970
+ ] })
750
971
  },
751
972
  idx
752
973
  )),
@@ -765,26 +986,38 @@ function QuestionChatPanel({
765
986
  value: inputValue,
766
987
  onChange: (e) => setInputValue(e.target.value),
767
988
  onKeyPress: handleKeyPress,
768
- placeholder: "Ask about this question...",
989
+ placeholder: isListening ? "Listening..." : "Ask about this question...",
769
990
  style: panelStyles.input,
770
- disabled: isLoading,
991
+ disabled: isLoading || isListening,
771
992
  "data-testid": "input-chat-message"
772
993
  }
773
994
  ),
995
+ isSpeechSupported && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
996
+ "button",
997
+ {
998
+ onClick: isListening ? stopListening : startListening,
999
+ disabled: isLoading,
1000
+ style: {
1001
+ ...panelStyles.buttonBase,
1002
+ ...isListening ? panelStyles.micButtonActive : panelStyles.micButton
1003
+ },
1004
+ "data-testid": "button-voice-input",
1005
+ title: isListening ? "Stop listening" : "Speak your question",
1006
+ children: isListening ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MicOffIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MicIcon, {})
1007
+ }
1008
+ ),
774
1009
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
775
1010
  "button",
776
1011
  {
777
1012
  onClick: () => sendMessage(inputValue),
778
1013
  disabled: isLoading || !inputValue.trim(),
779
1014
  style: {
1015
+ ...panelStyles.buttonBase,
780
1016
  ...panelStyles.sendButton,
781
1017
  ...isLoading || !inputValue.trim() ? panelStyles.sendButtonDisabled : {}
782
1018
  },
783
1019
  "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
- ] })
1020
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SendIcon, {})
788
1021
  }
789
1022
  )
790
1023
  ] })
@@ -1774,7 +2007,7 @@ function QuizPlayer({
1774
2007
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: defaultStyles.quizContent, children: [
1775
2008
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { ...defaultStyles.question, position: "relative", paddingBottom: "40px" }, children: [
1776
2009
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: defaultStyles.questionText, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TextToSpeech, { text: currentQuestion.question, inline: true, size: "md" }) }),
1777
- isExtraQuestion && !showFeedback && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2010
+ isExtraQuestion && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1778
2011
  "button",
1779
2012
  {
1780
2013
  onClick: () => setShowSkipModal(true),
@@ -1815,7 +2048,7 @@ function QuizPlayer({
1815
2048
  ]
1816
2049
  }
1817
2050
  ),
1818
- !isExtraQuestion && !showFeedback && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2051
+ !isExtraQuestion && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1819
2052
  "button",
1820
2053
  {
1821
2054
  onClick: () => setShowReportModal(true),