4 Commits

Author SHA1 Message Date
juanpabloacosta 60267f23d2 Linked correct video to question. 2023-12-06 03:39:30 -05:00
juanpabloacosta 3d3af9a412 Removed duplicate word. 2023-12-06 03:29:11 -05:00
juanpabloacosta d0227f7f43 Added questions and related resources for Spanish lesson. 2023-12-06 03:25:47 -05:00
juanpabloacosta 78d5c841ea Tweaked AI prompt to provide more useful feedback, and more forgiving. 2023-12-06 03:25:14 -05:00
10 changed files with 102 additions and 61 deletions
+6 -1
View File
@@ -51,8 +51,13 @@ def ai_mark(request: AIMarkRequest):
marking_system_prompt = f"""
You are a marking system for a language learning app.
You are marking a question from a chapter on {request.chapter} in {request.language}.
Please mark the user's answer as correct or incorrect and give a reason for your marking.
Output in the following JSON format: {{"correct": bool, "reason": str}}
Your marking is primarily based on the user's answer being semantically equivalent, in the given scenario,
to the expected answer, rather than identical.
Ignore incorrect spacing and capitalisation in your grading.
Output in the following JSON format: {{"correct": bool, "reason": str}}.
"reason" should always state the expected answer and the user's answer, along with an explanation of the mistake.
"""
user_prompt = f"""
The question is: {request.question}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+81 -1
View File
@@ -101,7 +101,7 @@ export const chapters_jp: Chapter[] = [
// question: "Translate this sentence: Water please",
// wordBank: ['水', 'を', 'ください', 'おいしい', 'おもい', 'すし', '中', 'です'],
question: 'Translate this sentence: 水をください',
wordBank: ['I', 'sushi', 'cookies', 'want', 'please', 'give', 'rice', 'some', 'yesterday'],
wordBank: ['I', 'sushi', 'cookies', 'want', 'please', 'give', 'Water', 'some', 'yesterday'],
// expected: '水をください',
expected: 'Water please',
type: "written-question"
@@ -137,3 +137,83 @@ export const chapters_jp: Chapter[] = [
}]
}
]
export const chapters_es: Chapter[] = [
{
name: 'Order food',
steps: [{
questions: [
{
question: "Translate this phrase: Me provoca un helado",
wordBank: ['I', 'want', 'ice cream', 'an', 'urgent', 'am', 'craving', 'milkshake'],
expected: 'I want an ice cream',
type: "written-question"
},
{
question: 'Pastel',
pronunciation: 'Pastel (pahs-tehl)',
description: 'Pastel is the Spanish word for "cake". It is a popular dessert in many Spanish-speaking countries, also known as torta.',
example: 'Vamos a celebrar con un pastel! (Let\'s celebrate with a cake!)',
type: 'written-vocabulary',
},
{
question: 'What do you hear?',
wordBank: ['favor', 'ver', 'el', 'menú', 'por', 'Quiero', 'libro', 'risas', 'oler'],
expected: 'Quiero ver el menú por favor',
translation: 'I want to see the menu, please',
url: window.location.origin + '/audio/es_1_1_3.mp3',
type: 'verbal-question',
},
{
question: 'Please say: Este postre es exquisito',
translation: 'This dessert is exquisite',
type: 'verbal-pronunciation',
},
{
question: 'What is the filling of the tequeño?',
clipUrl: window.location.origin + "/video/tequeños.mp4",
description: "Tequeños are a popular Venezuelan appetizer. They are made of cheese wrapped in dough and fried.",
expected: 'Cheese (queso)',
type: 'video',
}
]
},
{
questions: [
{
question: "Translate this phrase: Yo quiero agua por favor",
wordBank: ['I', 'juice', 'water', 'want', 'please', 'give', 'some', 'now'],
expected: 'I want water please',
type: "written-question"
},
{
question: 'Jugo',
pronunciation: 'Jugo (hoo-goh)',
description: 'Jugo is the Spanish word for "juice". In some parts of the world, juice is also called zumo.',
example: 'Me gusta tomar jugo de naranja! (I like to drink orange juice!)',
type: 'written-vocabulary',
},
{
question: 'What do you hear?',
wordBank: ['Que', 'Quien', 'tomar', 'yo', 'tú', 'quieres', 'comer', 'de'],
expected: 'Que quieres de comer',
translation: 'What do you want to eat?',
url: window.location.origin + '/audio/es_1_2_3.mp3',
type: 'verbal-question',
},
{
question: 'Please say: La comida está deliciosa!',
translation: 'The food is delicious!',
type: 'verbal-pronunciation',
},
{
question: 'What is the boy going to eat?',
clipUrl: window.location.origin + "/video/paella.mp4",
description: "The boy is about to eat paella, a traditional Spanish dish. It is made of rice, seafood, and vegetables.",
expected: 'Paella',
type: 'video',
}
]
}]
}
]
+6 -3
View File
@@ -1,14 +1,16 @@
import MandarinChinese from '../assets/img/lang/zh.svg'
import Japanese from '../assets/img/lang/ja.svg'
import Spanish from '../assets/img/lang/es.svg'
import English from '../assets/img/lang/en.svg'
import {Chapter, chapters_jp} from "./CourseData";
import {Chapter, chapters_jp, chapters_es} from "./CourseData";
// db.users: Signup table map<username, password>
// db.user: Current logged-in user
const db = localStorage
const backendUrl = 'https://318-bk.hydev.org'
// const backendUrl = 'https://318-bk.hydev.org'
const backendUrl = "https://127.0.0.1:8000"
export interface Lang {
name: string
@@ -20,6 +22,7 @@ export interface Lang {
export const possibleLangs: Lang[] = [
// {name: 'Mandarin Chinese', code: 'zh', icon: MandarinChinese, data: []},
{name: 'Japanese', code: 'ja', icon: Japanese, data: chapters_jp},
{name: 'Spanish', code: 'es', icon: Spanish, data: chapters_es},
// {name: 'English', code: 'en', icon: English, data: []},
]
@@ -30,7 +33,7 @@ export function signup(username: string, password: string, language: string)
const users = JSON.parse(db.users)
users[username] = {password, language}
users[username] = {password, language, "experience": 10, "completed_modules": [], "day_streak": 0, "speaking": 0}
db.users = JSON.stringify(users)
db.user = username
+4 -35
View File
@@ -3,7 +3,6 @@ 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();
@@ -13,8 +12,6 @@ export default function Character() {
type Message = { text: string, sender: string };
const [messages, setMessages] = useState<Message[]>([]);
const [isRecording, setIsRecording] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isOtherLoading, setIsOtherLoading] = useState(false);
let chunks = [] as any;
const mediaRecorder = useRef<MediaRecorder | null>(null);
@@ -27,7 +24,7 @@ export default function Character() {
useEffect(() => {
scrollToBottom()
}, [messages, isLoading]);
}, [messages]);
useEffect(() => {
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
@@ -38,17 +35,12 @@ 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);
@@ -56,8 +48,7 @@ export default function Character() {
const audioUrl = URL.createObjectURL(audioFileResponse);
const audio = new Audio(audioUrl);
audio.play();
setMessages(prevMessages => [...prevMessages, { text: msg, sender: 'other' }]);
setIsOtherLoading(false);
setMessages(prevMessages => [...prevMessages, { text: text, sender: 'me' }, { text: msg, sender: 'other' }]);
}
});
}, []);
@@ -83,8 +74,8 @@ export default function Character() {
<h1 className="text-center">Talk With...</h1>
<CharacterBadge name={name} image={image} onClick={() => {}}/>
<div className="chat-area pb-10">
{messages.length === 0 && !isLoading ? (
<p className="text-center text-gray-400">Please record a message to start the conversation.</p>
{messages.length === 0 ? (
<p className='subtext'>Please record a message to start the conversation.</p>
) : (
messages.map((message, index) => (
<div key={index} className={`message ${message.sender}`}>
@@ -92,28 +83,6 @@ export default function Character() {
</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 ref={messageEndRef} />
</div>
+1 -1
View File
@@ -12,7 +12,7 @@ export default function Review() {
const handleReviewLessonClick = (reviewType: string, lesson: string) => {
const lessons: _Question[] = getLanguage().data.flatMap(chapter => chapter.steps).flatMap(step => step.questions);
navigate('/lesson', { state: { questions: lessons.filter(it => it.type == `${reviewType}-${lesson}`), home: location.pathname } });
navigate('/lesson', { state: { questions: lessons.filter(it => it.type === `${reviewType}-${lesson}`), home: location.pathname } });
}
return (
+4 -20
View File
@@ -2,7 +2,6 @@ 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();
@@ -12,7 +11,6 @@ export default function Character() {
type Message = { text: string, sender: string };
const [messages, setMessages] = useState<Message[]>([]);
const [message, setMessage] = useState('');
const [isOtherLoading, setIsOtherLoading] = useState(false);
const messageEndRef = useRef<HTMLDivElement>(null);
@@ -24,16 +22,13 @@ export default function Character() {
scrollToBottom()
}, [messages]);
const handleSendClick = async () => {
const handleSendClick = () => {
if (message !== '') {
setMessages(prevMessages => [...prevMessages, { text: message, sender: 'me' }]);
setMessage('');
setIsOtherLoading(true);
const response = await humanChatMessage(sessionId, message);
const { msg } = response;
setMessages(prevMessages => [...prevMessages, { text: msg, sender: 'other' }]);
setIsOtherLoading(false);
humanChatMessage(sessionId, message).then((response) => {
setMessages(prevMessages => [...prevMessages, { text: response.msg, sender: 'other' }]);
})
}
}
@@ -60,17 +55,6 @@ export default function Character() {
</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>
</div>