switch to realtime api
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
981945a0f0
commit
233d26ae90
1 changed files with 68 additions and 28 deletions
68
src/App.jsx
68
src/App.jsx
|
@ -11,7 +11,6 @@ function App() {
|
||||||
const mediaRecorderRef = useRef(null);
|
const mediaRecorderRef = useRef(null);
|
||||||
const audioChunksRef = useRef([]);
|
const audioChunksRef = useRef([]);
|
||||||
const audioStreamRef = useRef(null);
|
const audioStreamRef = useRef(null);
|
||||||
const silenceTimerRef = useRef(null);
|
|
||||||
const canvasRef = useRef(null);
|
const canvasRef = useRef(null);
|
||||||
const analyserRef = useRef(null);
|
const analyserRef = useRef(null);
|
||||||
const dataArrayRef = useRef(null);
|
const dataArrayRef = useRef(null);
|
||||||
|
@ -114,7 +113,10 @@ Teacher: Good job! How about Magic – something special and powerful.
|
||||||
const { text } = await whisperRes.json();
|
const { text } = await whisperRes.json();
|
||||||
setTranscript(text);
|
setTranscript(text);
|
||||||
|
|
||||||
const chatRes = await fetch('https://api.openai.com/v1/chat/completions', {
|
setAiReply(''); // Clear previous reply
|
||||||
|
let fullText = '';
|
||||||
|
|
||||||
|
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${OPENAI_API_KEY}`,
|
'Authorization': `Bearer ${OPENAI_API_KEY}`,
|
||||||
|
@ -122,21 +124,29 @@ Teacher: Good job! How about Magic – something special and powerful.
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: 'gpt-4o',
|
model: 'gpt-4o',
|
||||||
|
stream: true,
|
||||||
messages: [
|
messages: [
|
||||||
{ role: 'system', content: systemContent },
|
{ role: 'system', content: systemContent },
|
||||||
{ role: 'user', content: text }
|
{ 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 reader = response.body.getReader();
|
||||||
|
const decoder = new TextDecoder("utf-8");
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
|
||||||
|
const chunk = decoder.decode(value);
|
||||||
|
const lines = chunk.split('\n').filter(line => line.trim() !== '');
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith('data: ')) {
|
||||||
|
const data = line.replace('data: ', '');
|
||||||
|
if (data === '[DONE]') {
|
||||||
|
// After full reply received, synthesize speech
|
||||||
const speechRes = await fetch('https://api.openai.com/v1/audio/speech', {
|
const speechRes = await fetch('https://api.openai.com/v1/audio/speech', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -146,17 +156,47 @@ Teacher: Good job! How about Magic – something special and powerful.
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: 'tts-1-hd',
|
model: 'tts-1-hd',
|
||||||
voice: 'nova',
|
voice: 'nova',
|
||||||
input: reply
|
input: fullText
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
const outputAudioBlob = await speechRes.blob();
|
const outputAudioBlob = await speechRes.blob();
|
||||||
const audioUrl = URL.createObjectURL(outputAudioBlob);
|
const audioUrl = URL.createObjectURL(outputAudioBlob);
|
||||||
const audio = new Audio(audioUrl);
|
const audio = new Audio(audioUrl);
|
||||||
audio.play();
|
audio.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
audio.onended = () => {
|
const parsed = JSON.parse(data);
|
||||||
// Do nothing here — wait for user to press the button
|
const delta = parsed.choices?.[0]?.delta?.content;
|
||||||
};
|
if (delta) {
|
||||||
|
fullText += delta;
|
||||||
|
setAiReply(prev => prev + delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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(outputAudioBlob);
|
||||||
|
// const audio = new Audio(audioUrl);
|
||||||
|
// audio.play();
|
||||||
|
|
||||||
|
// audio.onended = () => {
|
||||||
|
// // Do nothing here — wait for user to press the button
|
||||||
|
// };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue