2025-05-12 21:54:32 +07:00
import React , { useState , useRef } from 'react' ;
import './App.css' ;
const OPENAI _API _KEY = import . meta . env . VITE _OPENAI _API _KEY ;
function App ( ) {
const [ transcript , setTranscript ] = useState ( '' ) ;
const [ aiReply , setAiReply ] = useState ( '' ) ;
const [ isRecording , setIsRecording ] = useState ( false ) ;
const mediaRecorderRef = useRef ( null ) ;
const audioChunksRef = useRef ( [ ] ) ;
const startRecording = async ( ) => {
const stream = await navigator . mediaDevices . getUserMedia ( { audio : true } ) ;
mediaRecorderRef . current = new MediaRecorder ( stream ) ;
audioChunksRef . current = [ ] ;
mediaRecorderRef . current . ondataavailable = ( e ) => {
audioChunksRef . current . push ( e . data ) ;
} ;
mediaRecorderRef . current . onstop = async ( ) => {
const audioBlob = new Blob ( audioChunksRef . current , { type : 'audio/webm' } ) ;
const formData = new FormData ( ) ;
formData . append ( 'file' , audioBlob , 'input.webm' ) ;
formData . append ( 'model' , 'whisper-1' ) ;
const whisperRes = await fetch ( 'https://api.openai.com/v1/audio/transcriptions' , {
method : 'POST' ,
headers : {
Authorization : ` Bearer ${ OPENAI _API _KEY } `
} ,
body : formData
} ) ;
const { text } = await whisperRes . json ( ) ;
setTranscript ( text ) ;
const chatRes = await fetch ( 'https://api.openai.com/v1/chat/completions' , {
method : 'POST' ,
headers : {
'Authorization' : ` Bearer ${ OPENAI _API _KEY } ` ,
'Content-Type' : 'application/json'
} ,
body : JSON . stringify ( {
model : 'gpt-4o' ,
messages : [
2025-05-15 16:50:55 +07:00
{ role : 'system' , content : 'You are an English tutor that is kind, fun to be around and can teach English language lessons through adventurous stories very well. You are assigned to talk with a primary school EFL student about a movie they watched yesterday.' } ,
2025-05-12 21:54:32 +07:00
{ role : 'user' , content : text }
]
} )
} ) ;
const chatData = await chatRes . json ( ) ;
if ( ! chatData . choices || ! chatData . choices [ 0 ] ) {
console . error ( 'Chat API response error:' , chatData ) ;
setAiReply ( 'Sorry, something went wrong with the AI response.' ) ;
return ;
}
const reply = chatData . choices [ 0 ] . message . content ;
setAiReply ( reply ) ;
const speechRes = await fetch ( 'https://api.openai.com/v1/audio/speech' , {
method : 'POST' ,
headers : {
'Authorization' : ` Bearer ${ OPENAI _API _KEY } ` ,
'Content-Type' : 'application/json'
} ,
body : JSON . stringify ( {
model : 'tts-1-hd' ,
voice : 'nova' ,
input : reply
} )
} ) ;
const outputAudioBlob = await speechRes . blob ( ) ;
const audioUrl = URL . createObjectURL ( audioBlob ) ;
const audio = new Audio ( audioUrl ) ;
audio . play ( ) ;
} ;
mediaRecorderRef . current . start ( ) ;
setIsRecording ( true ) ;
} ;
const stopRecording = ( ) => {
mediaRecorderRef . current . stop ( ) ;
2025-05-16 15:21:02 +07:00
// Stop all tracks on the media stream
if ( mediaRecorderRef . current . stream ) {
mediaRecorderRef . current . stream . getTracks ( ) . forEach ( track => track . stop ( ) ) ;
}
2025-05-12 21:54:32 +07:00
setIsRecording ( false ) ;
} ;
return (
< div className = "min-h-screen bg-gray-100 flex flex-col justify-end p-4" >
2025-05-15 16:50:55 +07:00
< h1 > AI English Tutor < / h1 >
< div > เว ็ บไซต ์ น ี ้ เป ็ นการทำ Proof of concept ของการใช ้ AI ในการสอนภาษาอ ั งกฤษ โดยเน ้ นไปท ี ่ การจำลองการส ื ่ อสารด ้ วยเส ี ยง < / div >
< div > คร ู ม ี ท ั กษะเสร ิ มสร ้ างจ ิ นตนาการ สน ุ ก ตลก ให ้ ข ้ อค ิ ด สามารถสอนเน ื ้ อหาผ ่ านเร ื ่ องราวการผจญภ ั ยได ้ ด ี เช ่ น คำศ ั พท ์ หร ื อข ้ อค ิ ด คำถามปลายเป ิ ด เป ็ นต ้ น < / div >
< div > AI ได ้ ร ั บบทให ้ พ ู ดค ุ ยก ั บน ั กเร ี ยนในเร ื ่ องภาพยนตร ์ ท ี ่ ได ้ ร ั บชมไปเม ื ่ อวาน < / div >
< h3 > ว ิ ธ ี ใช ้ งาน < / h3 >
< div > กดป ุ ่ ม Speak ด ้ านล ่ าง อน ุ ญาตให ้ ใช ้ ไมโครโฟน พ ู ดค ุ ยก ั บ AI เป ็ นภาษาอ ั งกฤษ แล ้ วกด Stop < / div >
< div > เร ิ ่ มจากการท ั กทายก ั บ AI แล ้ วเล ่ าให ้ ฟ ั งว ่ าเม ื ่ อวานร ั บชมภาพยนตร ์ เร ื ่ องใดมา AI จะถามค ุ ณเก ี ่ ยวก ั บภาพยนตร ์ เร ื ่ องน ั ้ น < / div >
2025-05-12 21:54:32 +07:00
< button
onClick = { isRecording ? stopRecording : startRecording }
className = { ` mt-4 p-3 rounded-xl font-semibold text-white ${ isRecording ? 'bg-red-500' : 'bg-blue-500' } ` }
>
{ isRecording ? 'Stop' : 'Speak' }
< / button >
2025-05-15 16:50:55 +07:00
< div className = "mb-2 bg-white rounded-2xl p-4 shadow text-sm" >
< div > < strong > You : < / strong > { transcript } < / div >
< div className = "mt-2" > < strong > Tutor : < / strong > { aiReply } < / div >
< / div >
2025-05-12 21:54:32 +07:00
< / div >
) ;
}
export default App ;