@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 +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +258 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +258 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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 &&
|
|
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 &&
|
|
2051
|
+
!isExtraQuestion && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1819
2052
|
"button",
|
|
1820
2053
|
{
|
|
1821
2054
|
onClick: () => setShowReportModal(true),
|