diff --git a/frontend/src/components/LessonComplete.tsx b/frontend/src/components/LessonComplete.tsx new file mode 100644 index 0000000..e7d55f3 --- /dev/null +++ b/frontend/src/components/LessonComplete.tsx @@ -0,0 +1,26 @@ +import DuoSplash from "../assets/img/duo-splash.png"; +import { useNavigate } from "react-router-dom"; + +interface LessonCompleteProps { + home: string; + } + +export default function LessonComplete({ home } : LessonCompleteProps) +{ + const navigate = useNavigate(); + + return
+
+ Duolingo Logo + +

Well done!

+ +

+ Experience Gained: +100XP +

+ +
+
+} diff --git a/frontend/src/pages/Character.tsx b/frontend/src/pages/Character.tsx index 24769ff..b2a64ff 100644 --- a/frontend/src/pages/Character.tsx +++ b/frontend/src/pages/Character.tsx @@ -3,6 +3,7 @@ import { Icon } from '@iconify/react'; import CharacterBadge from '../components/CharacterBadge'; import { useState, useEffect, useRef, useCallback } from 'react'; import { speechToText, characterChatMessage, getAudio } from '../logic/sdk'; +import { SyncLoader } from 'react-spinners'; export default function Character() { const location = useLocation(); @@ -12,6 +13,8 @@ export default function Character() { type Message = { text: string, sender: string }; const [messages, setMessages] = useState([]); const [isRecording, setIsRecording] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isOtherLoading, setIsOtherLoading] = useState(false); let chunks = [] as any; const mediaRecorder = useRef(null); @@ -24,7 +27,7 @@ export default function Character() { useEffect(() => { scrollToBottom() - }, [messages]); + }, [messages, isLoading]); useEffect(() => { navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => { @@ -35,12 +38,17 @@ export default function Character() { mediaRecorder.current.onstop = async (e) => { setIsRecording(false); + setIsLoading(true); const blob = new Blob(chunks, { type: 'audio/wav' }); chunks = []; const audioFile = new File([blob], "audio.wav", { type: 'audio/wav' }); const text = await speechToText(audioFile); + setMessages(prevMessages => [...prevMessages, { text: text, sender: 'me' }]); + setIsLoading(false); + + setIsOtherLoading(true); const response = await characterChatMessage(sessionId, text); const { msg, audio_id } = response; const audioBlob = await getAudio(audio_id); @@ -48,7 +56,8 @@ export default function Character() { const audioUrl = URL.createObjectURL(audioFileResponse); const audio = new Audio(audioUrl); audio.play(); - setMessages(prevMessages => [...prevMessages, { text: text, sender: 'me' }, { text: msg, sender: 'other' }]); + setMessages(prevMessages => [...prevMessages, { text: msg, sender: 'other' }]); + setIsOtherLoading(false); } }); }, []); @@ -74,8 +83,8 @@ export default function Character() {

Talk With...

{}}/>
- {messages.length === 0 ? ( -

Please record a message to start the conversation.

+ {messages.length === 0 && !isLoading ? ( +

Please record a message to start the conversation.

) : ( messages.map((message, index) => (
@@ -83,6 +92,28 @@ export default function Character() {
)) )} + {isLoading && ( +
+ +
+ )} + {isOtherLoading && ( +
+ +
+ )}
diff --git a/frontend/src/pages/Lesson.tsx b/frontend/src/pages/Lesson.tsx index 3c4d39c..8a0c699 100644 --- a/frontend/src/pages/Lesson.tsx +++ b/frontend/src/pages/Lesson.tsx @@ -5,6 +5,7 @@ import WrittenVocabularyExercise from "../components/WrittenVocabularyExercise" import VerbalQuestionsExercise from "../components/VerbalQuestionsExercise" import Progress from '../components/Progress'; import VerbalPronunciationExercise from '../components/VerbalPronunciationExercise'; +import LessonComplete from '../components/LessonComplete'; import {_Question, chapters_jp, Question} from "../logic/CourseData"; import VideoExercise from "../components/VideoExercise"; @@ -24,27 +25,28 @@ export default function Lesson() const onSubmit = () => { - if (currQuestion < questions.length - 1) - setCurrQuestion(currQuestion + 1); - else navigate(home); + setCurrQuestion(currQuestion + 1); } const renderQuestion = (currIndex: number) => { + if (currIndex >= questions.length) { + return ; + } const chapter = 'Ordering food'; const question: Question = questions[currQuestion]; switch (question.type) { case 'written-question': - return ; + return ; case 'written-vocabulary': - return ; + return ; case 'verbal-question': - return ; + return ; case 'verbal-pronunciation': - return ; + return ; case 'video': - return ; + return ; default: return null; } diff --git a/frontend/src/pages/UserChat.tsx b/frontend/src/pages/UserChat.tsx index 19ac848..1130052 100644 --- a/frontend/src/pages/UserChat.tsx +++ b/frontend/src/pages/UserChat.tsx @@ -2,6 +2,7 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { Icon } from '@iconify/react'; import { useState, useRef, useEffect } from 'react'; import { humanChatMessage } from '../logic/sdk'; +import { SyncLoader } from 'react-spinners'; export default function Character() { const location = useLocation(); @@ -11,6 +12,7 @@ export default function Character() { type Message = { text: string, sender: string }; const [messages, setMessages] = useState([]); const [message, setMessage] = useState(''); + const [isOtherLoading, setIsOtherLoading] = useState(false); const messageEndRef = useRef(null); @@ -22,13 +24,16 @@ export default function Character() { scrollToBottom() }, [messages]); - const handleSendClick = () => { + const handleSendClick = async () => { if (message !== '') { setMessages(prevMessages => [...prevMessages, { text: message, sender: 'me' }]); setMessage(''); - humanChatMessage(sessionId, message).then((response) => { - setMessages(prevMessages => [...prevMessages, { text: response.msg, sender: 'other' }]); - }) + + setIsOtherLoading(true); + const response = await humanChatMessage(sessionId, message); + const { msg } = response; + setMessages(prevMessages => [...prevMessages, { text: msg, sender: 'other' }]); + setIsOtherLoading(false); } } @@ -55,6 +60,17 @@ export default function Character() { )) )} + {isOtherLoading && ( +
+ +
+ )}