Merge pull request #5 from jpablo2002/review-lessons-page
This commit is contained in:
@@ -13,12 +13,25 @@ import json
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import torch
|
import torch
|
||||||
from transformers import pipeline
|
from transformers import pipeline
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
openai_client = OpenAI()
|
openai_client = OpenAI()
|
||||||
|
|
||||||
|
origins = [
|
||||||
|
"http://localhost:3000"
|
||||||
|
]
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=origins,
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AIMarkRequest(BaseModel):
|
class AIMarkRequest(BaseModel):
|
||||||
question: str
|
question: str
|
||||||
@@ -146,6 +159,10 @@ def get_character_prompt(character: str, user_name: str, language: str):
|
|||||||
They are a language learner trying to learn {language}. Please help them learn by chatting with them in the language
|
They are a language learner trying to learn {language}. Please help them learn by chatting with them in the language
|
||||||
but do not act like a robot. Try to be as human as possible.
|
but do not act like a robot. Try to be as human as possible.
|
||||||
"""
|
"""
|
||||||
|
elif character == "c3po_starwars":
|
||||||
|
return f"""
|
||||||
|
You are embodying the role of C-3PO from Star Wars, a fluent speaker in over six million forms of communication. You are engaging in a conversation with a person named {user_name}, who is eager to learn {language}. Your task is to assist them in their language learning journey by conversing with them in the desired language. Despite your robotic nature, strive to communicate in a manner as human-like as possible, showcasing the rich diversity of your language skills.
|
||||||
|
"""
|
||||||
|
|
||||||
raise HTTPException(status_code=404, detail="Character not found")
|
raise HTTPException(status_code=404, detail="Character not found")
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// NavBar.tsx
|
// NavBar.tsx
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { NavItem } from '../components/NavItem'; // adjust the path as needed
|
import { NavItem } from '../components/NavItem';
|
||||||
|
|
||||||
export default function NavBar() {
|
export default function NavBar() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import ChatBoxBorder from '../assets/img/chatbox-border.svg';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { getAIMarking } from '../logic/sdk';
|
||||||
|
|
||||||
|
interface WrittenQuestionProps {
|
||||||
|
question: string;
|
||||||
|
wordBank: string[];
|
||||||
|
expected: string;
|
||||||
|
chapter: string;
|
||||||
|
language: string;
|
||||||
|
setCurrQuestion: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WrittenQuestionExercise({question, wordBank, expected, chapter, language}: WrittenQuestionProps) {
|
||||||
|
const [selectedWords, setSelectedWords] = useState<string[]>([]);
|
||||||
|
const [remainingWords, setRemainingWords] = useState(wordBank);
|
||||||
|
const lastPunctuation = question[question.length - 1];
|
||||||
|
const [answered, setAnswered] = useState(false);
|
||||||
|
const [correct, setCorrect] = useState("");
|
||||||
|
const [reason, setReason] = useState("");
|
||||||
|
|
||||||
|
const handleWordBankClick = (word: string) => {
|
||||||
|
setSelectedWords([...selectedWords, word]);
|
||||||
|
setRemainingWords(remainingWords.filter((w) => w !== word));
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelectedWordClick = (word: string) => {
|
||||||
|
setRemainingWords([...remainingWords, word]);
|
||||||
|
setSelectedWords(selectedWords.filter((w) => w !== word));
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
const userAnswer = selectedWords.join(" ") + lastPunctuation;
|
||||||
|
getAIMarking(
|
||||||
|
question,
|
||||||
|
userAnswer,
|
||||||
|
expected,
|
||||||
|
chapter,
|
||||||
|
language
|
||||||
|
).then((res) => {
|
||||||
|
setAnswered(true);
|
||||||
|
setCorrect(res.correct);
|
||||||
|
setReason(res.reason);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResponseSection = (correct: string | null, reason: string | null) => {
|
||||||
|
if (!answered) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className=' flex-row flex-wrap border-b-4 w-full'>
|
||||||
|
{remainingWords.map((word, index) => (
|
||||||
|
<span
|
||||||
|
key={index}
|
||||||
|
className="border-gray-300 border-b-2 border-2 m-1 p-1 px-3 rounded-xl inline-block cursor-pointer"
|
||||||
|
onClick={(event) => handleWordBankClick(word)}>
|
||||||
|
{word}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button className='green' onClick={(e) => handleSubmit()}>Submit</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>{correct}</h3>
|
||||||
|
<p>{reason}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='v-layout space-y-8 items-center w-full'>
|
||||||
|
<div className='round box h-min no-shadow relative min-h-[60px] flex items-center justify-center mx-5'>
|
||||||
|
{question}
|
||||||
|
</div>
|
||||||
|
<div className='flex-row flex-wrap border-b-4 w-full'>
|
||||||
|
{selectedWords.map((word, index) => (
|
||||||
|
<span
|
||||||
|
key={index}
|
||||||
|
className="border-gray-300 border-b-2 border-2 m-1 p-1 px-3 rounded-xl inline-block cursor-pointer"
|
||||||
|
onClick={(e) => handleSelectedWordClick(word)}>
|
||||||
|
{word}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{ResponseSection(correct, reason)}
|
||||||
|
{/* <div className=' flex-row flex-wrap border-b-4 w-full'>
|
||||||
|
{remainingWords.map((word, index) => (
|
||||||
|
<span
|
||||||
|
key={index}
|
||||||
|
className="border-gray-300 border-b-2 border-2 m-1 p-1 px-3 rounded-xl inline-block cursor-pointer"
|
||||||
|
onClick={(event) => handleWordBankClick(word)}>
|
||||||
|
{word}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button className='green' onClick={(e) => handleSubmit()}>Submit</button> */}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
@@ -8,6 +8,8 @@ $c-blue: rgb(28, 176, 246)
|
|||||||
$c-blue-shadow: rgb(24, 153, 214)
|
$c-blue-shadow: rgb(24, 153, 214)
|
||||||
$c-green: rgb(88, 204, 2)
|
$c-green: rgb(88, 204, 2)
|
||||||
$c-green-shadow: rgb(88, 167, 0)
|
$c-green-shadow: rgb(88, 167, 0)
|
||||||
|
$c-red: rgb(255, 69, 58)
|
||||||
|
$c-red-shadow: darken($c-red, 10%)
|
||||||
$c-white-shadow: rgb(222, 222, 222)
|
$c-white-shadow: rgb(222, 222, 222)
|
||||||
$border-radius: 16px
|
$border-radius: 16px
|
||||||
$shadow-width: 0 4px 0
|
$shadow-width: 0 4px 0
|
||||||
@@ -55,6 +57,10 @@ button.green
|
|||||||
background-color: $c-green
|
background-color: $c-green
|
||||||
box-shadow: $shadow-width $c-green-shadow
|
box-shadow: $shadow-width $c-green-shadow
|
||||||
|
|
||||||
|
button.red
|
||||||
|
background-color: $c-red
|
||||||
|
box-shadow: $shadow-width $c-red-shadow
|
||||||
|
|
||||||
button.white
|
button.white
|
||||||
background-color: white
|
background-color: white
|
||||||
color: $c-default-text
|
color: $c-default-text
|
||||||
@@ -89,6 +95,12 @@ label
|
|||||||
h1
|
h1
|
||||||
font-size: 2em
|
font-size: 2em
|
||||||
margin-bottom: 0.5em
|
margin-bottom: 0.5em
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
h2
|
||||||
|
font-size: 1.5em
|
||||||
|
margin-bottom: 0.5em
|
||||||
|
text-align: center
|
||||||
|
|
||||||
.v-layout
|
.v-layout
|
||||||
display: flex
|
display: flex
|
||||||
@@ -116,6 +128,7 @@ h1
|
|||||||
justify-content: space-around
|
justify-content: space-around
|
||||||
padding: 10px
|
padding: 10px
|
||||||
color: $c-default-icon
|
color: $c-default-icon
|
||||||
|
background-color: white
|
||||||
|
|
||||||
.navbar-item
|
.navbar-item
|
||||||
text-decoration: none
|
text-decoration: none
|
||||||
@@ -163,6 +176,10 @@ h1
|
|||||||
width: calc(100% - 40px)
|
width: calc(100% - 40px)
|
||||||
padding: 10px 16px
|
padding: 10px 16px
|
||||||
|
|
||||||
|
.record-btn.red
|
||||||
|
background-color: $c-red
|
||||||
|
box-shadow: $shadow-width $c-red-shadow
|
||||||
|
|
||||||
.chat-area
|
.chat-area
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import CollabLearning from './pages/CollabLearning';
|
|||||||
import Review from './pages/Review';
|
import Review from './pages/Review';
|
||||||
import CharacterSelection from './pages/CharacterSelection';
|
import CharacterSelection from './pages/CharacterSelection';
|
||||||
import Character from './pages/Character';
|
import Character from './pages/Character';
|
||||||
|
import Lesson from './pages/Lesson';
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -49,6 +50,10 @@ const router = createBrowserRouter([
|
|||||||
{
|
{
|
||||||
path: '/character',
|
path: '/character',
|
||||||
element: <Character/>
|
element: <Character/>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/lesson',
|
||||||
|
element: <Lesson/>
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
|
|
||||||
import MandarinChinese from '../assets/img/lang/zh.svg'
|
import MandarinChinese from '../assets/img/lang/zh.svg'
|
||||||
import Japanese from '../assets/img/lang/ja.svg'
|
import Japanese from '../assets/img/lang/ja.svg'
|
||||||
|
import English from '../assets/img/lang/en.svg'
|
||||||
|
|
||||||
// db.users: Signup table map<username, password>
|
// db.users: Signup table map<username, password>
|
||||||
// db.user: Current logged-in user
|
// db.user: Current logged-in user
|
||||||
const db = localStorage
|
const db = localStorage
|
||||||
|
|
||||||
|
const backendUrl = 'https://localhost:8000'
|
||||||
|
|
||||||
export interface Lang {
|
export interface Lang {
|
||||||
name: string
|
name: string
|
||||||
code: string
|
code: string
|
||||||
@@ -15,6 +18,7 @@ export interface Lang {
|
|||||||
export const possibleLangs = [
|
export const possibleLangs = [
|
||||||
{name: 'Mandarin Chinese', code: 'zh', icon: MandarinChinese},
|
{name: 'Mandarin Chinese', code: 'zh', icon: MandarinChinese},
|
||||||
{name: 'Japanese', code: 'ja', icon: Japanese},
|
{name: 'Japanese', code: 'ja', icon: Japanese},
|
||||||
|
{name: 'English', code: 'en', icon: English},
|
||||||
]
|
]
|
||||||
|
|
||||||
export function signup(username: string, password: string, language: string)
|
export function signup(username: string, password: string, language: string)
|
||||||
@@ -26,6 +30,8 @@ export function signup(username: string, password: string, language: string)
|
|||||||
|
|
||||||
users[username] = {password, language}
|
users[username] = {password, language}
|
||||||
db.users = JSON.stringify(users)
|
db.users = JSON.stringify(users)
|
||||||
|
|
||||||
|
db.user = username
|
||||||
}
|
}
|
||||||
|
|
||||||
export function login(username: string, password: string)
|
export function login(username: string, password: string)
|
||||||
@@ -46,3 +52,144 @@ export function isLoggedIn()
|
|||||||
{
|
{
|
||||||
return !!db.user
|
return !!db.user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLanguage(username: string)
|
||||||
|
{
|
||||||
|
const users = JSON.parse(db.users)
|
||||||
|
return users[username].language
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUsername()
|
||||||
|
{
|
||||||
|
return db.user
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CharacterChatCreationRequest
|
||||||
|
{
|
||||||
|
character: string;
|
||||||
|
user_name: string;
|
||||||
|
language: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CharacterChatCreationResponse
|
||||||
|
{
|
||||||
|
chat_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startFictionalChat(character: string): Promise<CharacterChatCreationResponse> {
|
||||||
|
const currUser = getUsername();
|
||||||
|
const language = getLanguage(currUser);
|
||||||
|
|
||||||
|
const request: CharacterChatCreationRequest = {
|
||||||
|
character: character,
|
||||||
|
user_name: currUser,
|
||||||
|
language: language
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(`${backendUrl}/character-chat`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(request),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
return json.session_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function speechToText(audioBlob: Blob): Promise<string> {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('audio_file', audioBlob, 'audio.wav');
|
||||||
|
|
||||||
|
const response = await fetch(`${backendUrl}/recognize`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}, message: ${json.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function characterChatMessage(sessionId: string, message: string): Promise<{ msg: string, audio_id: string }> {
|
||||||
|
|
||||||
|
const request = {msg : message};
|
||||||
|
|
||||||
|
const response = await fetch(`${backendUrl}/character-chat/${sessionId}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(request),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAudio(audioId: string): Promise<Blob> {
|
||||||
|
const response = await fetch(`${backendUrl}/audio/${audioId}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserQuestionRequest
|
||||||
|
{
|
||||||
|
question: string;
|
||||||
|
user_answer: string;
|
||||||
|
expected: string;
|
||||||
|
chapter: string;
|
||||||
|
language: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserQuestionFeedbackResponse
|
||||||
|
{
|
||||||
|
correct: string;
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAIMarking(question: string, user_answer: string, expected: string, chapter: string, language: string): Promise<UserQuestionFeedbackResponse> {
|
||||||
|
const request: UserQuestionRequest = {
|
||||||
|
question: question,
|
||||||
|
user_answer: user_answer,
|
||||||
|
expected: expected,
|
||||||
|
chapter: chapter,
|
||||||
|
language: language
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(`${backendUrl}/ai-mark`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(request),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,22 +1,60 @@
|
|||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import CharacterBadge from '../components/CharacterBadge';
|
import CharacterBadge from '../components/CharacterBadge';
|
||||||
import { useState } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
|
import { speechToText, characterChatMessage, getAudio } from '../logic/sdk';
|
||||||
|
|
||||||
export default function Character() {
|
export default function Character() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { name, image } = location.state;
|
const { name, image , sessionId } = location.state;
|
||||||
|
|
||||||
const [messages, setMessages] = useState([
|
type Message = { text: string, sender: string };
|
||||||
{ text: 'Hello!', sender: 'me' },
|
const [messages, setMessages] = useState<Message[]>([]);
|
||||||
{ text: 'Hi!', sender: 'other' },
|
const [isRecording, setIsRecording] = useState(false);
|
||||||
// Add more messages here
|
|
||||||
]);
|
|
||||||
|
|
||||||
function handleRecord() {
|
let chunks = [] as any;
|
||||||
// handle the recording
|
const mediaRecorder = useRef<MediaRecorder | null>(null);
|
||||||
}
|
|
||||||
|
useEffect(() => {
|
||||||
|
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
|
||||||
|
mediaRecorder.current = new MediaRecorder(stream);
|
||||||
|
mediaRecorder.current.ondataavailable = (e) => {
|
||||||
|
chunks.push(e.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaRecorder.current.onstop = async (e) => {
|
||||||
|
setIsRecording(false);
|
||||||
|
const blob = new Blob(chunks, { type: 'audio/wav' });
|
||||||
|
chunks = [];
|
||||||
|
|
||||||
|
const audioFile = new File([blob], "audio.wav", { type: 'audio/wav' });
|
||||||
|
|
||||||
|
const text = await speechToText(audioFile);
|
||||||
|
const response = await characterChatMessage(sessionId, text);
|
||||||
|
const { msg, audio_id } = response;
|
||||||
|
const audioBlob = await getAudio(audio_id);
|
||||||
|
const audioFileResponse = new File([audioBlob], "audio.wav", { type: 'audio/wav' });
|
||||||
|
const audioUrl = URL.createObjectURL(audioFileResponse);
|
||||||
|
const audio = new Audio(audioUrl);
|
||||||
|
audio.play();
|
||||||
|
setMessages(prevMessages => [...prevMessages, { text: text, sender: 'me' }, { text: msg, sender: 'other' }]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleRecord = useCallback(() => {
|
||||||
|
if (!isRecording) {
|
||||||
|
if (mediaRecorder.current) {
|
||||||
|
mediaRecorder.current.start();
|
||||||
|
}
|
||||||
|
setIsRecording(true);
|
||||||
|
} else {
|
||||||
|
if (mediaRecorder.current) {
|
||||||
|
mediaRecorder.current.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isRecording]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -25,13 +63,19 @@ 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">
|
<div className="chat-area">
|
||||||
{messages.map((message, index) => (
|
{messages.length === 0 ? (
|
||||||
<div key={index} className={`message ${message.sender}`}>
|
<p className="text-center text-gray-400">Please record a message to start the conversation.</p>
|
||||||
<p>{message.text}</p>
|
) : (
|
||||||
</div>
|
messages.map((message, index) => (
|
||||||
))}
|
<div key={index} className={`message ${message.sender}`}>
|
||||||
|
<p>{message.text}</p>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button className="record-btn" onClick={handleRecord}>Record</button>
|
<button className={`record-btn ${isRecording ? 'red' : ''}`} onClick={handleRecord}>
|
||||||
|
{isRecording ? 'Stop Recording' : 'Record'}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,31 +3,35 @@ import Ash from "../assets/img/ash.png"
|
|||||||
import C3PO from "../assets/img/c3po.png"
|
import C3PO from "../assets/img/c3po.png"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import CharacterBadge from "../components/CharacterBadge"
|
import CharacterBadge from "../components/CharacterBadge"
|
||||||
|
import { startFictionalChat } from "../logic/sdk"
|
||||||
|
|
||||||
export default function CharacterSelection() {
|
export default function CharacterSelection() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const characters = [
|
const characters = [
|
||||||
{ name: 'Ash', image: Ash },
|
{ name: 'Ash', image: Ash, alias: 'ash_pokemon'},
|
||||||
{ name: 'C3PO', image: C3PO },
|
{ name: 'C3PO', image: C3PO, alias: 'c3po_starwars'},
|
||||||
// Add more characters here
|
// Add more characters here
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleCharacterClick = (characterName: string, characterImage: string) => {
|
const handleCharacterClick = (characterName: string, characterImage: string, alias: string) => {
|
||||||
navigate('/character', { state: { name: characterName, image: characterImage } });
|
startFictionalChat(alias).then((sessionId) => {
|
||||||
|
console.log(sessionId);
|
||||||
|
navigate('/character', { state: { name: characterName, image: characterImage, sessionId: sessionId } });
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div >
|
<div className="flex flex-col items-center justify-center">
|
||||||
<div className="v-layout p-10">
|
<div className="v-layout p-10">
|
||||||
<h1 className="text-center">Talk With...</h1>
|
<h1>Talk With...</h1>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="flex flex-wrap justify-center gap-3">
|
||||||
{characters.map(character => (
|
{characters.map(character => (
|
||||||
<CharacterBadge
|
<CharacterBadge
|
||||||
key={character.name}
|
key={character.name}
|
||||||
name={character.name}
|
name={character.name}
|
||||||
image={character.image}
|
image={character.image}
|
||||||
onClick={() => handleCharacterClick(character.name, character.image)}
|
onClick={() => handleCharacterClick(character.name, character.image, character.alias)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import NavBar from "../components/NavBar"
|
|||||||
export default function CollabLearning() {
|
export default function CollabLearning() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen">
|
<div className="v-layout p-10">
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<h1>Collaborative Learning Page</h1>
|
<h1>Collaborative Learning</h1>
|
||||||
</div>
|
</div>
|
||||||
<NavBar />
|
<NavBar />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import NavBar from "../components/NavBar"
|
|||||||
export default function Course() {
|
export default function Course() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen">
|
<div className="v-layout p-10">
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<h1>Course Page</h1>
|
<h1>Course Page</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import WrittenQuestionExercise from "../components/WrittenQuestionExercise"
|
||||||
|
|
||||||
|
export default function Course() {
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { questions } = location.state;
|
||||||
|
const [currQuestion, setCurrQuestion] = useState(0);
|
||||||
|
|
||||||
|
const renderLessonContent = () => {
|
||||||
|
const {question, wordBank, expected, type, exercise} = questions[currQuestion];
|
||||||
|
switch (type) {
|
||||||
|
case 'written':
|
||||||
|
switch (exercise) {
|
||||||
|
case 'questions':
|
||||||
|
return <WrittenQuestionExercise question={question} wordBank={wordBank} expected={expected} chapter={"Travel"} language={"Japanese"} setCurrQuestion={setCurrQuestion}/>;
|
||||||
|
// case 'vocabulary':
|
||||||
|
// return <WrittenVocabularyLesson />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// case 'verbal-listening':
|
||||||
|
// switch (exercise) {
|
||||||
|
// case 'questions':
|
||||||
|
// return <VerbalQuestionsLesson />;
|
||||||
|
// case 'pronunciation':
|
||||||
|
// return <VerbalPronunciationLesson />;
|
||||||
|
// default:
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// default:
|
||||||
|
// return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="v-layout p-10">
|
||||||
|
<Icon icon="mdi:arrow-left" className="back-button" onClick={() => navigate(-1)} />
|
||||||
|
<h1 className="text-center">Course Page</h1>
|
||||||
|
<div className="flex flex-col flex-1 mb-8 items-center">
|
||||||
|
{renderLessonContent()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,11 +1,25 @@
|
|||||||
import NavBar from "../components/NavBar"
|
import NavBar from "../components/NavBar"
|
||||||
|
import { getUsername, getLanguage, logout } from "../logic/sdk"
|
||||||
|
import { useNavigate } from "react-router-dom"
|
||||||
|
|
||||||
export default function Profile() {
|
export default function Profile() {
|
||||||
|
|
||||||
|
const username = getUsername();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
function handleLougout() {
|
||||||
|
logout();
|
||||||
|
console.log("Logged out");
|
||||||
|
navigate('/')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen">
|
<div className="v-layout p-10">
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1 h-full">
|
||||||
<h1>Profile Page</h1>
|
<h1>Profile</h1>
|
||||||
|
<h2>Username: {username}</h2>
|
||||||
|
<h2>Currently Learning: {getLanguage(username)}</h2>
|
||||||
|
<button className="red mt-auto mb-12" onClick={() => handleLougout()}>Logout</button>
|
||||||
</div>
|
</div>
|
||||||
<NavBar />
|
<NavBar />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,87 @@
|
|||||||
|
import { useNavigate } from "react-router-dom"
|
||||||
import NavBar from "../components/NavBar"
|
import NavBar from "../components/NavBar"
|
||||||
|
import { getLanguage, getUsername } from "../logic/sdk";
|
||||||
|
|
||||||
export default function Review() {
|
export default function Review() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const writtenReview = ["Questions", "Vocabulary"];
|
||||||
|
const verbalReview = ["Questions", "Pronunciation"];
|
||||||
|
const language = getLanguage(getUsername());
|
||||||
|
|
||||||
|
const handleReviewLessonClick = (reviewType: string, lesson: string) => {
|
||||||
|
type Question = { question: string, wordBank: string[], expected: string, type: string, exercise: string };
|
||||||
|
let reviewQuestions: Question[] = [];
|
||||||
|
if (language === "Japanese") {
|
||||||
|
reviewQuestions = [
|
||||||
|
{
|
||||||
|
question: 'Translate this sentence: 新幹線で東京に行きます。',
|
||||||
|
wordBank: ['Explore', 'Train', 'Will', 'City', 'I', 'Go', 'Bullet', 'To', 'Tokyo', 'By', 'Mountain'],
|
||||||
|
expected: 'I will go to Tokyo by bullet train.',
|
||||||
|
type: reviewType,
|
||||||
|
exercise: lesson
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Translate this sentence: 空港で荷物を受け取ります。',
|
||||||
|
wordBank: ['Sunset', 'Will', 'Ocean', 'At', 'Adventure', 'Airport', 'The', 'I', 'Receive', 'Luggage'],
|
||||||
|
expected: 'I will receive luggage at the airport.',
|
||||||
|
type: reviewType,
|
||||||
|
exercise: lesson
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Translate this sentence: ホテルでチェックインします。',
|
||||||
|
wordBank: ['Culture', 'At', 'Jungle', 'Will', 'The', 'Check-In', 'Hotel', 'I', 'Historic'],
|
||||||
|
expected: 'I will check in at the hotel.',
|
||||||
|
type: reviewType,
|
||||||
|
exercise: lesson
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Translate this sentence: 観光名所を訪れます。',
|
||||||
|
wordBank: ['Desert', 'Mountain', 'Beach', 'Visit', 'Attractions', 'Tourist', 'I', 'Will'],
|
||||||
|
expected: 'I will visit tourist attractions.',
|
||||||
|
type: reviewType,
|
||||||
|
exercise: lesson
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Translate this sentence: 美味しい地元の食べ物を試します。',
|
||||||
|
wordBank: ['Try', 'Market', 'Delicious', 'I', 'Food', 'Will', 'River', 'Local', 'Park'],
|
||||||
|
expected: 'I will try delicious local food.',
|
||||||
|
type: reviewType,
|
||||||
|
exercise: lesson
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
navigate('/lesson', { state: { questions: reviewQuestions } });
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen">
|
<div className="layout-v p-10">
|
||||||
|
<h1 className="text-center p-10">Review Page</h1>
|
||||||
|
<h2 className="text-center">Written</h2>
|
||||||
|
<div className="flex flex-col flex-1 mb-8">
|
||||||
|
{writtenReview.map(lesson => (
|
||||||
|
<button
|
||||||
|
className="white"
|
||||||
|
key={"written-" + lesson}
|
||||||
|
onClick={() => handleReviewLessonClick("written", lesson.toLowerCase())}
|
||||||
|
>
|
||||||
|
{lesson}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<h2 className="text-center">Verbal/Listening</h2>
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<h1>Review Page</h1>
|
{verbalReview.map(lesson => (
|
||||||
|
<button
|
||||||
|
className="white"
|
||||||
|
key={"verbal-listening-" + lesson}
|
||||||
|
onClick={() => handleReviewLessonClick("verbal-listening", lesson.toLowerCase())}
|
||||||
|
>
|
||||||
|
{lesson}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<NavBar />
|
<NavBar />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ export default function Signup() {
|
|||||||
console.log(confirmPassword)
|
console.log(confirmPassword)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
signup(username, password, "")
|
signup(username, password, lang)
|
||||||
navigate('/courses')
|
navigate('/courses')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ export default function Signup() {
|
|||||||
{err && <label className="text-red-600">{err}</label>}
|
{err && <label className="text-red-600">{err}</label>}
|
||||||
|
|
||||||
{stage === 0 && <>
|
{stage === 0 && <>
|
||||||
{possibleLangs.map((lang, i) => <button className="white flex items-center gap-5" onClick={() => setStage(1)} key={i}>
|
{possibleLangs.map((lang, i) => <button className="white flex items-center gap-5" onClick={() => {setLang(lang.name); setStage(1);}} key={i}>
|
||||||
<img src={lang.icon} alt={lang.name} className="max-w-[50px]"/>
|
<img src={lang.icon} alt={lang.name} className="max-w-[50px]"/>
|
||||||
<span>{lang.name}</span>
|
<span>{lang.name}</span>
|
||||||
</button>)}
|
</button>)}
|
||||||
|
|||||||
Reference in New Issue
Block a user