Skip to content

Commit

Permalink
Merge pull request #443 from l3vels/fix/voice-preferences
Browse files Browse the repository at this point in the history
Fix/voice preferences
  • Loading branch information
Chkhikvadze committed Feb 29, 2024
2 parents 88c7e97 + 94ee7be commit 2c85a2a
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 110 deletions.
4 changes: 4 additions & 0 deletions apps/server/agents/conversational/conversational.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ async def run(
},
)

yield res

try:
configs = agent_with_configs.configs
voice_url = None
Expand All @@ -109,6 +111,8 @@ async def run(
except Exception as err:
res = f"{res}\n\n{handle_agent_error(err)}"

yield res

history.create_ai_message(
res,
human_message_id,
Expand Down
56 changes: 55 additions & 1 deletion apps/server/services/voice.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ def text_to_speech(

synthesizers = {
"142e60f5-2d46-4b1a-9054-0764e553eed6": playht_text_to_speech,
# TODO: add AzureVoice.id: azure_text_to_speech, when available.
"509fd791-578f-40be-971f-c6753957c307": eleven_labs_text_to_speech,
"dc872426-a95c-4c41-83a2-5e5ed43670cd": azure_text_to_speech,
}

if configs.synthesizer not in synthesizers:
Expand Down Expand Up @@ -147,3 +148,56 @@ async def deepgram_speech_to_text(
return transcribed_text.strip('"')
except Exception as err:
raise TranscriberException(str(err))


def eleven_labs_text_to_speech(
text: str, configs: ConfigsOutput, settings: AccountVoiceSettings
) -> bytes:
if settings.ELEVEN_LABS_API_KEY is None or not settings.ELEVEN_LABS_API_KEY:
raise SynthesizerException(
"Please set Eleven Labs API Key in [Voice Integrations](/integrations/voice/elevenlabs) in order to synthesize text to speech."
)

url = f"https://api.elevenlabs.io/v1/text-to-speech/{configs.voice_id or configs.default_voice}"

payload = {
"model_id": "eleven_multilingual_v2",
"text": text,
"voice_settings": {"similarity_boost": 1, "stability": 1, "style": 1},
}
headers = {
"xi-api-key": settings.ELEVEN_LABS_API_KEY,
"Content-Type": "application/json",
}

response = requests.post(url, json=payload, headers=headers)
return response.content


def azure_text_to_speech(
text: str, configs: ConfigsOutput, settings: AccountVoiceSettings
) -> bytes:
if settings.AZURE_SPEECH_KEY is None or not settings.AZURE_SPEECH_KEY:
raise SynthesizerException(
"Please set Azure Speech Key in [Voice Integrations](/integrations/voice/azure) in order to synthesize text to speech."
)

url = f"https://{settings.AZURE_SPEECH_REGION}.tts.speech.microsoft.com/cognitiveservices/v1"

body = f"""
<speak version='1.0' xml:lang='en-US'>
<voice xml:lang='en-US' name='{configs.voice_id}'>
{text}
</voice>
</speak>
"""

headers = {
"Ocp-Apim-Subscription-Key": settings.AZURE_SPEECH_KEY,
"Content-Type": "application/ssml+xml",
"X-Microsoft-OutputFormat": "riff-24khz-16bit-mono-pcm",
"User-Agent": "Your application name",
}

response = requests.post(url, headers=headers, data=body)
return response.content
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import HumanReply from './components/HumanReply'
import AiReply from './components/AiReply'
import { ReplyStateProps } from '../ReplyBox'

import { ArrowDown } from 'share-ui/components/Icon/Icons'

export enum MessageTypeEnum {
AI_MANUAL = 'AI_MANUAL',
User = 'User',
Expand Down Expand Up @@ -49,6 +51,7 @@ const ChatMessageListV2 = ({
const [listIsReady, setListIsReady] = useState(false)

const virtuoso = useRef<VirtuosoHandle>(null)
const scrollerRef = useRef<any>(null)

const filteredData = data?.map((chat: any) => {
const chatDate = moment(chat?.created_on).format('HH:mm')
Expand Down Expand Up @@ -95,7 +98,7 @@ const ChatMessageListV2 = ({

const scrollToEnd = () => {
virtuoso.current?.scrollToIndex({
index: initialChat.length + 1,
index: initialChat.length + 2,
align: 'end',
})
}
Expand Down Expand Up @@ -150,10 +153,44 @@ const ChatMessageListV2 = ({
// eslint-disable-next-line
}, [sessionId])

const [showScrollButton, setShowScrollButton] = useState(false)

useEffect(() => {
const handleScroll = () => {
if (scrollerRef.current) {
const { scrollTop, clientHeight, scrollHeight } = scrollerRef.current
const bottomScrollPosition = scrollTop + clientHeight
const isScrollable = scrollHeight - bottomScrollPosition > 1 // Using 1 as a threshold
setShowScrollButton(isScrollable)
}
}

const scrollContainer = scrollerRef.current
if (scrollContainer) {
scrollContainer.addEventListener('scroll', handleScroll)

// Initial check in case the list is already scrollable
handleScroll()
}

return () => {
if (scrollContainer) {
scrollContainer.removeEventListener('scroll', handleScroll)
}
}
}, [initialChat])

useEffect(() => {
if (!showScrollButton) scrollToEnd()
}, [data])

return (
<StyledRoot show={true}>
<Virtuoso
ref={virtuoso}
scrollerRef={ref => {
scrollerRef.current = ref
}}
style={{ height: '100%' }}
data={initialChat}
totalCount={data.length}
Expand Down Expand Up @@ -253,13 +290,20 @@ const ChatMessageListV2 = ({
</>
)}
/>
{showScrollButton && (
<StyledScrollButton onClick={scrollToEnd}>
<ArrowDown size={18} />
</StyledScrollButton>
)}
</StyledRoot>
)
}

export default memo(ChatMessageListV2)

const StyledRoot = styled.div<{ show: boolean }>`
position: relative;
opacity: 0;
width: 100%;
Expand Down Expand Up @@ -331,3 +375,21 @@ const StyledReplyMessageContainer = styled.div`
justify-content: center;
/* align-items: center; */
`
const StyledScrollButton = styled.div`
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
cursor: pointer;
width: 38px;
height: 38px;
border-radius: 100px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); // Add box shadow here
display: flex;
align-items: center;
justify-content: center;
`
84 changes: 3 additions & 81 deletions apps/ui/src/pages/Agents/AgentForm/AgentForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import AgentRunners, { StyledRunnerFieldsWrapper } from './components/AgentRunne
import { isVoiceAgent } from 'utils/agentUtils'

import VoicePreferences from './FormSections/VoicePreferences'
import RadioButton from 'share-ui/components/RadioButton/RadioButton'

type AgentFormProps = {
formik: any
Expand All @@ -52,6 +53,8 @@ const AgentForm = ({ formik, isVoice = true }: AgentFormProps) => {
agent_integrations,
agent_type,
agent_sentiment_analyzer,
agent_voice_response,
agent_voice_input_mode,
} = values

const {
Expand Down Expand Up @@ -259,87 +262,6 @@ const AgentForm = ({ formik, isVoice = true }: AgentFormProps) => {
voiceSynthesizerOptions={voiceSynthesizerOptions}
voiceTranscriberOptions={voiceTranscriberOptions}
/>
{/* <StyledFormInputWrapper>
<TypographyPrimary
value={t('response-mode')}
type={Typography.types.LABEL}
size={Typography.sizes.md}
/>
<RadioButton
text={t('text')}
name='agent_voice_response'
onSelect={() => setFieldValue('agent_voice_response', ['Text'])}
checked={
agent_voice_response?.length === 1 && agent_voice_response?.includes('Text')
}
/>
<RadioButton
text={t('voice')}
name='agent_voice_response'
onSelect={() => setFieldValue('agent_voice_response', ['Voice'])}
checked={
agent_voice_response?.length === 1 &&
agent_voice_response?.includes('Voice')
}
/>
<RadioButton
text={`${t('text')} & ${t('voice')}`}
name='agent_voice_response'
onSelect={() => setFieldValue('agent_voice_response', ['Text', 'Voice'])}
checked={agent_voice_response?.length === 2}
/>
</StyledFormInputWrapper> */}

{/* <StyledFormInputWrapper>
<TypographyPrimary
value={t('input-mode')}
type={Typography.types.LABEL}
size={Typography.sizes.md}
/>
<StyledCheckboxWrapper>
<Checkbox
label={t('text')}
kind='secondary'
// name='agent_is_template'
checked={agent_voice_input_mode?.includes('Text')}
onChange={() => {
if (agent_voice_input_mode?.includes('Text')) {
const filteredInput = agent_voice_input_mode?.filter(
(input: string) => input !== 'Text',
)
setFieldValue('agent_voice_input_mode', filteredInput)
} else {
setFieldValue('agent_voice_input_mode', [
...agent_voice_input_mode,
'Text',
])
}
}}
/>
</StyledCheckboxWrapper>
<StyledCheckboxWrapper>
<Checkbox
label={t('voice')}
kind='secondary'
// name='agent_is_template'
checked={agent_voice_input_mode?.includes('Voice')}
onChange={() => {
if (agent_voice_input_mode?.includes('Voice')) {
const filteredInput = agent_voice_input_mode?.filter(
(input: string) => input !== 'Voice',
)
setFieldValue('agent_voice_input_mode', filteredInput)
} else {
setFieldValue('agent_voice_input_mode', [
...agent_voice_input_mode,
'Voice',
])
}
}}
/>
</StyledCheckboxWrapper>
</StyledFormInputWrapper> */}
</>
</TabPanel>

Expand Down
Loading

0 comments on commit 2c85a2a

Please sign in to comment.