feat: improve read aloud (#105, #341)

This commit is contained in:
josc146
2023-05-18 10:17:33 +08:00
parent 6bb5a81304
commit 277b9f69a9
2 changed files with 24 additions and 20 deletions
+2 -3
View File
@@ -21,6 +21,7 @@ export function ConversationItem({ type, content, session, done, port, onRetry }
<p>{t('You')}:</p>
<div className="gpt-util-group">
<CopyButton contentFn={() => content.replace(/\n<hr\/>$/, '')} size={14} />
<ReadButton contentFn={() => content} size={14} />
{!collapsed ? (
<span
title={t('Collapse')}
@@ -74,9 +75,7 @@ export function ConversationItem({ type, content, session, done, port, onRetry }
{session && (
<CopyButton contentFn={() => content.replace(/\n<hr\/>$/, '')} size={14} />
)}
{session && (
<ReadButton contentFn={() => content.replace(/\n<hr\/>$/, '')} size={14} />
)}
{session && <ReadButton contentFn={() => content} size={14} />}
{!collapsed ? (
<span
title={t('Collapse')}
+22 -17
View File
@@ -1,7 +1,8 @@
import { useState } from 'react'
import { UnmuteIcon, MuteIcon } from '@primer/octicons-react'
import { MuteIcon, UnmuteIcon } from '@primer/octicons-react'
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
import { useConfig } from '../../hooks/use-config.mjs'
ReadButton.propTypes = {
contentFn: PropTypes.func.isRequired,
@@ -9,44 +10,48 @@ ReadButton.propTypes = {
className: PropTypes.string,
}
const synth = window.speechSynthesis
function ReadButton({ className, contentFn, size }) {
const { t } = useTranslation()
const [speaking, setSpeaking] = useState(false)
const config = useConfig()
const startSpeak = () => {
speechSynthesis.cancel()
let text = contentFn()
synth.cancel()
const text = contentFn()
const utterance = new SpeechSynthesisUtterance(text)
const voices = synth.getVoices()
let voice
if (config.preferredLanguage.includes('en') && navigator.language.includes('en'))
voice = voices.find((v) => v.name.toLowerCase().includes('microsoft aria'))
else if (config.preferredLanguage.includes('zh') || navigator.language.includes('zh'))
voice = voices.find((v) => v.name.toLowerCase().includes('xiaoyi'))
if (!voice) voice = voices.find((v) => v.lang.substring(0, 2) === config.preferredLanguage)
if (!voice) voice = voices.find((v) => v.lang === navigator.language)
Object.assign(utterance, {
// lang: 'zh-CN',
rate: 0.9,
rate: 1,
volume: 1,
onend: () => setSpeaking(false),
onerror: () => setSpeaking(false),
voice: voice,
})
let supportedVoices = speechSynthesis.getVoices()
for (let i = 0; i < supportedVoices.length; i++) {
if (supportedVoices[i].lang.indexOf(text[0]) >= 0) {
utterance.voice = supportedVoices[i]
break
}
}
speechSynthesis.speak(utterance)
synth.speak(utterance)
setSpeaking(true)
}
const stopSpeak = () => {
speechSynthesis.cancel()
synth.cancel()
setSpeaking(false)
}
return (
<span
title={t('Read')}
title={t('Read Aloud')}
className={`gpt-util-icon ${className ? className : ''}`}
onClick={speaking ? stopSpeak : startSpeak}
>