Commit 401799c6 authored by Timothy J. Baek's avatar Timothy J. Baek
Browse files

feat: tts optimisation

parent 68ed24b7
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
import CodeBlock from './CodeBlock.svelte'; import CodeBlock from './CodeBlock.svelte';
import { synthesizeOpenAISpeech } from '$lib/apis/openai'; import { synthesizeOpenAISpeech } from '$lib/apis/openai';
import { extractSentences } from '$lib/utils';
export let modelfiles = []; export let modelfiles = [];
export let message; export let message;
...@@ -35,8 +36,10 @@ ...@@ -35,8 +36,10 @@
let tooltipInstance = null; let tooltipInstance = null;
let audioMap = {}; let sentencesAudio = {};
let speaking = null; let speaking = null;
let speakingIdx = null;
let loadingSpeech = false; let loadingSpeech = false;
$: tokens = marked.lexer(message.content); $: tokens = marked.lexer(message.content);
...@@ -116,44 +119,67 @@ ...@@ -116,44 +119,67 @@
} }
}; };
const playAudio = (idx) => {
return new Promise((res) => {
speakingIdx = idx;
const audio = sentencesAudio[idx];
audio.play();
audio.onended = async (e) => {
await new Promise((r) => setTimeout(r, 500));
if (Object.keys(sentencesAudio).length - 1 === idx) {
speaking = null;
}
res(e);
};
});
};
const toggleSpeakMessage = async () => { const toggleSpeakMessage = async () => {
if (speaking) { if (speaking) {
speechSynthesis.cancel(); speechSynthesis.cancel();
speaking = null;
audioMap[message.id].pause(); sentencesAudio[speakingIdx].pause();
audioMap[message.id].currentTime = 0; sentencesAudio[speakingIdx].currentTime = 0;
speaking = null;
speakingIdx = null;
} else { } else {
speaking = true; speaking = true;
if ($settings?.speech?.engine === 'openai') { if ($settings?.speech?.engine === 'openai') {
loadingSpeech = true; loadingSpeech = true;
const res = await synthesizeOpenAISpeech(
localStorage.token, const sentences = extractSentences(message.content);
$settings?.speech?.speaker, console.log(sentences);
message.content
).catch((error) => { sentencesAudio = sentences.reduce((a, e, i, arr) => {
toast.error(error); a[i] = null;
return null; return a;
}); }, {});
if (res) { let lastPlayedAudioPromise = Promise.resolve(); // Initialize a promise that resolves immediately
const blob = await res.blob();
const blobUrl = URL.createObjectURL(blob); for (const [idx, sentence] of sentences.entries()) {
console.log(blobUrl); const res = await synthesizeOpenAISpeech(
localStorage.token,
loadingSpeech = false; $settings?.speech?.speaker,
sentence
const audio = new Audio(blobUrl); ).catch((error) => {
audioMap[message.id] = audio; toast.error(error);
return null;
audio.onended = () => { });
speaking = null;
if ($settings.conversationMode) { if (res) {
document.getElementById('voice-input-button')?.click(); const blob = await res.blob();
} const blobUrl = URL.createObjectURL(blob);
}; const audio = new Audio(blobUrl);
audio.play().catch((e) => console.error('Error playing audio:', e)); sentencesAudio[idx] = audio;
loadingSpeech = false;
lastPlayedAudioPromise = lastPlayedAudioPromise.then(() => playAudio(idx));
}
} }
} else { } else {
let voices = []; let voices = [];
......
...@@ -324,3 +324,20 @@ export const isValidHttpUrl = (string) => { ...@@ -324,3 +324,20 @@ export const isValidHttpUrl = (string) => {
return url.protocol === 'http:' || url.protocol === 'https:'; return url.protocol === 'http:' || url.protocol === 'https:';
}; };
export const removeEmojis = (str) => {
// Regular expression to match emojis
const emojiRegex = /[\uD800-\uDBFF][\uDC00-\uDFFF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g;
// Replace emojis with an empty string
return str.replace(emojiRegex, '');
};
export const extractSentences = (text) => {
// Split the paragraph into sentences based on common punctuation marks
const sentences = text.split(/(?<=[.!?])/);
return sentences
.map((sentence) => removeEmojis(sentence.trim()))
.filter((sentence) => sentence !== '');
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment