Merge branch 'master' of https://github.com/hykilpikonna/CSC318
Deploy to GH Pages / build-and-deploy (push) Has been cancelled
Deploy to GH Pages / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -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 <div className="flex flex-col justify-center">
|
||||||
|
<div className="flex flex-col p-5 gap-5 items-center">
|
||||||
|
<img src={DuoSplash} alt="Duolingo Logo"></img>
|
||||||
|
|
||||||
|
<h1>Well done!</h1>
|
||||||
|
|
||||||
|
<p className="white">
|
||||||
|
Experience Gained: +100XP
|
||||||
|
</p>
|
||||||
|
<button className="green" onClick={() => navigate(home)}>
|
||||||
|
Continue
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { Icon } from '@iconify/react';
|
|||||||
import CharacterBadge from '../components/CharacterBadge';
|
import CharacterBadge from '../components/CharacterBadge';
|
||||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { speechToText, characterChatMessage, getAudio } from '../logic/sdk';
|
import { speechToText, characterChatMessage, getAudio } from '../logic/sdk';
|
||||||
|
import { SyncLoader } from 'react-spinners';
|
||||||
|
|
||||||
export default function Character() {
|
export default function Character() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -12,6 +13,8 @@ export default function Character() {
|
|||||||
type Message = { text: string, sender: string };
|
type Message = { text: string, sender: string };
|
||||||
const [messages, setMessages] = useState<Message[]>([]);
|
const [messages, setMessages] = useState<Message[]>([]);
|
||||||
const [isRecording, setIsRecording] = useState(false);
|
const [isRecording, setIsRecording] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isOtherLoading, setIsOtherLoading] = useState(false);
|
||||||
|
|
||||||
let chunks = [] as any;
|
let chunks = [] as any;
|
||||||
const mediaRecorder = useRef<MediaRecorder | null>(null);
|
const mediaRecorder = useRef<MediaRecorder | null>(null);
|
||||||
@@ -24,7 +27,7 @@ export default function Character() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
}, [messages]);
|
}, [messages, isLoading]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
|
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
|
||||||
@@ -35,12 +38,17 @@ export default function Character() {
|
|||||||
|
|
||||||
mediaRecorder.current.onstop = async (e) => {
|
mediaRecorder.current.onstop = async (e) => {
|
||||||
setIsRecording(false);
|
setIsRecording(false);
|
||||||
|
setIsLoading(true);
|
||||||
const blob = new Blob(chunks, { type: 'audio/wav' });
|
const blob = new Blob(chunks, { type: 'audio/wav' });
|
||||||
chunks = [];
|
chunks = [];
|
||||||
|
|
||||||
const audioFile = new File([blob], "audio.wav", { type: 'audio/wav' });
|
const audioFile = new File([blob], "audio.wav", { type: 'audio/wav' });
|
||||||
|
|
||||||
const text = await speechToText(audioFile);
|
const text = await speechToText(audioFile);
|
||||||
|
setMessages(prevMessages => [...prevMessages, { text: text, sender: 'me' }]);
|
||||||
|
setIsLoading(false);
|
||||||
|
|
||||||
|
setIsOtherLoading(true);
|
||||||
const response = await characterChatMessage(sessionId, text);
|
const response = await characterChatMessage(sessionId, text);
|
||||||
const { msg, audio_id } = response;
|
const { msg, audio_id } = response;
|
||||||
const audioBlob = await getAudio(audio_id);
|
const audioBlob = await getAudio(audio_id);
|
||||||
@@ -48,7 +56,8 @@ export default function Character() {
|
|||||||
const audioUrl = URL.createObjectURL(audioFileResponse);
|
const audioUrl = URL.createObjectURL(audioFileResponse);
|
||||||
const audio = new Audio(audioUrl);
|
const audio = new Audio(audioUrl);
|
||||||
audio.play();
|
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() {
|
|||||||
<h1 className="text-center">Talk With...</h1>
|
<h1 className="text-center">Talk With...</h1>
|
||||||
<CharacterBadge name={name} image={image} onClick={() => {}}/>
|
<CharacterBadge name={name} image={image} onClick={() => {}}/>
|
||||||
<div className="chat-area pb-10">
|
<div className="chat-area pb-10">
|
||||||
{messages.length === 0 ? (
|
{messages.length === 0 && !isLoading ? (
|
||||||
<p className='subtext'>Please record a message to start the conversation.</p>
|
<p className="text-center text-gray-400">Please record a message to start the conversation.</p>
|
||||||
) : (
|
) : (
|
||||||
messages.map((message, index) => (
|
messages.map((message, index) => (
|
||||||
<div key={index} className={`message ${message.sender}`}>
|
<div key={index} className={`message ${message.sender}`}>
|
||||||
@@ -83,6 +92,28 @@ export default function Character() {
|
|||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="message me">
|
||||||
|
<SyncLoader
|
||||||
|
color="white"
|
||||||
|
loading={isLoading}
|
||||||
|
aria-label="Loading Spinner"
|
||||||
|
data-testid="loader"
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isOtherLoading && (
|
||||||
|
<div className="message other">
|
||||||
|
<SyncLoader
|
||||||
|
color="white"
|
||||||
|
loading={isOtherLoading}
|
||||||
|
aria-label="Loading Spinner"
|
||||||
|
data-testid="loader"
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div ref={messageEndRef} />
|
<div ref={messageEndRef} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import WrittenVocabularyExercise from "../components/WrittenVocabularyExercise"
|
|||||||
import VerbalQuestionsExercise from "../components/VerbalQuestionsExercise"
|
import VerbalQuestionsExercise from "../components/VerbalQuestionsExercise"
|
||||||
import Progress from '../components/Progress';
|
import Progress from '../components/Progress';
|
||||||
import VerbalPronunciationExercise from '../components/VerbalPronunciationExercise';
|
import VerbalPronunciationExercise from '../components/VerbalPronunciationExercise';
|
||||||
|
import LessonComplete from '../components/LessonComplete';
|
||||||
import {_Question, chapters_jp, Question} from "../logic/CourseData";
|
import {_Question, chapters_jp, Question} from "../logic/CourseData";
|
||||||
import VideoExercise from "../components/VideoExercise";
|
import VideoExercise from "../components/VideoExercise";
|
||||||
|
|
||||||
@@ -24,27 +25,28 @@ export default function Lesson()
|
|||||||
|
|
||||||
const onSubmit = () =>
|
const onSubmit = () =>
|
||||||
{
|
{
|
||||||
if (currQuestion < questions.length - 1)
|
|
||||||
setCurrQuestion(currQuestion + 1);
|
setCurrQuestion(currQuestion + 1);
|
||||||
else navigate(home);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderQuestion = (currIndex: number) =>
|
const renderQuestion = (currIndex: number) =>
|
||||||
{
|
{
|
||||||
|
if (currIndex >= questions.length) {
|
||||||
|
return <LessonComplete home={home}/>;
|
||||||
|
}
|
||||||
const chapter = 'Ordering food';
|
const chapter = 'Ordering food';
|
||||||
const question: Question = questions[currQuestion];
|
const question: Question = questions[currQuestion];
|
||||||
switch (question.type)
|
switch (question.type)
|
||||||
{
|
{
|
||||||
case 'written-question':
|
case 'written-question':
|
||||||
return <WrittenQuestionExercise q={question} chapter={chapter} onSubmit={onSubmit}/>;
|
return <WrittenQuestionExercise key={currQuestion} q={question} chapter={chapter} onSubmit={onSubmit}/>;
|
||||||
case 'written-vocabulary':
|
case 'written-vocabulary':
|
||||||
return <WrittenVocabularyExercise q={question} onSubmit={onSubmit}/>;
|
return <WrittenVocabularyExercise key={currQuestion} q={question} onSubmit={onSubmit}/>;
|
||||||
case 'verbal-question':
|
case 'verbal-question':
|
||||||
return <VerbalQuestionsExercise q={question} chapter={chapter} onSubmit={onSubmit}/>;
|
return <VerbalQuestionsExercise key={currQuestion} q={question} chapter={chapter} onSubmit={onSubmit}/>;
|
||||||
case 'verbal-pronunciation':
|
case 'verbal-pronunciation':
|
||||||
return <VerbalPronunciationExercise q={question} chapter={chapter} onSubmit={onSubmit}/>;
|
return <VerbalPronunciationExercise key={currQuestion} q={question} chapter={chapter} onSubmit={onSubmit}/>;
|
||||||
case 'video':
|
case 'video':
|
||||||
return <VideoExercise q={question} chapter={chapter} onSubmit={onSubmit}/>;
|
return <VideoExercise key={currQuestion} q={question} chapter={chapter} onSubmit={onSubmit}/>;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { humanChatMessage } from '../logic/sdk';
|
import { humanChatMessage } from '../logic/sdk';
|
||||||
|
import { SyncLoader } from 'react-spinners';
|
||||||
|
|
||||||
export default function Character() {
|
export default function Character() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -11,6 +12,7 @@ export default function Character() {
|
|||||||
type Message = { text: string, sender: string };
|
type Message = { text: string, sender: string };
|
||||||
const [messages, setMessages] = useState<Message[]>([]);
|
const [messages, setMessages] = useState<Message[]>([]);
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
|
const [isOtherLoading, setIsOtherLoading] = useState(false);
|
||||||
|
|
||||||
const messageEndRef = useRef<HTMLDivElement>(null);
|
const messageEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -22,13 +24,16 @@ export default function Character() {
|
|||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
const handleSendClick = () => {
|
const handleSendClick = async () => {
|
||||||
if (message !== '') {
|
if (message !== '') {
|
||||||
setMessages(prevMessages => [...prevMessages, { text: message, sender: 'me' }]);
|
setMessages(prevMessages => [...prevMessages, { text: message, sender: 'me' }]);
|
||||||
setMessage('');
|
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() {
|
|||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
{isOtherLoading && (
|
||||||
|
<div className="message other">
|
||||||
|
<SyncLoader
|
||||||
|
color="white"
|
||||||
|
loading={isOtherLoading}
|
||||||
|
aria-label="Loading Spinner"
|
||||||
|
data-testid="loader"
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div ref={messageEndRef} />
|
<div ref={messageEndRef} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user