Skip to content

Commit

Permalink
Merge pull request #442 from l3vels/feat/voice-options
Browse files Browse the repository at this point in the history
Feat/voice options
  • Loading branch information
Chkhikvadze committed Feb 26, 2024
2 parents 4289a33 + 8c3fd89 commit 88c7e97
Show file tree
Hide file tree
Showing 13 changed files with 498 additions and 115 deletions.
74 changes: 72 additions & 2 deletions apps/server/controllers/voice.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
from typing import List
import os
from typing import List, Optional

from fastapi import APIRouter
import requests
from fastapi import APIRouter, Depends
from fastapi_sqlalchemy import db

from models.config import ConfigModel
from typings.auth import UserAccount
from typings.voice import VoiceOutput
from utils.auth import (authenticate, authenticate_by_any,
authenticate_by_token_or_api_key)
from voices.get_voices import get_all_voice_providers

router = APIRouter()
Expand All @@ -19,3 +26,66 @@ def get_voices() -> List[VoiceOutput]:

return get_all_voice_providers()
# return convert_tools_to_tool_list(db_tools)


@router.get("/options")
def get_voice_options(
page: Optional[int] = 1,
per_page: Optional[int] = 10,
auth: UserAccount = Depends(authenticate_by_token_or_api_key),
):
"""
Get all voice options.
"""

voice_settings = ConfigModel.get_account_voice_settings(db.session, auth.account.id)

PLAY_HT_API_KEY = voice_settings.PLAY_HT_API_KEY
PLAY_HT_USER_ID = voice_settings.PLAY_HT_USER_ID
ELEVEN_LABS_API_KEY = voice_settings.ELEVEN_LABS_API_KEY

AZURE_SPEECH_KEY = voice_settings.AZURE_SPEECH_KEY
AZURE_SPEECH_REGION = voice_settings.AZURE_SPEECH_REGION

# ElevenLabs
labsUrl = os.environ.get("ELEVEN_LABS_VOICE_LIST_API")
labsHeaders = {
"xi-api-key": ELEVEN_LABS_API_KEY or os.environ.get("ELEVEN_LABS_API_KEY")
}
labsResponse = requests.get(labsUrl, headers=labsHeaders)

# PlayHT
playHtUrl = os.environ.get("PLAY_HT_VOICE_LIST_API")
playHtHeaders = {
"accept": "application/json",
"AUTHORIZATION": PLAY_HT_API_KEY or os.environ.get("PLAY_HT_API_KEY"),
"X-USER-ID": PLAY_HT_USER_ID or os.environ.get("PLAY_HT_USER_ID"),
}
playHtResponse = requests.get(playHtUrl, headers=playHtHeaders)
play_ht_all_voices = playHtResponse.json()

start = (page - 1) * per_page
end = start + per_page
paginated_voices = play_ht_all_voices[start:end]

# Azure
azureUrl = os.environ.get("AZURE_VOICE_LIST_API")
azureHeaders = {
"Ocp-Apim-Subscription-Key": AZURE_SPEECH_KEY
or os.environ.get("AZURE_SPEECH_KEY"),
}
azureResponse = requests.get(azureUrl, headers=azureHeaders)
azure_voices = azureResponse.json()

start = (page - 1) * per_page
end = start + per_page
paginated_azure_voices = azure_voices[start:end]

print(labsResponse.json())
combined_response = {
"elevenLabsVoices": labsResponse.json(),
"playHtVoices": play_ht_all_voices,
"azureVoices": azure_voices,
}

return combined_response
2 changes: 2 additions & 0 deletions apps/server/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ def get_account_voice_settings(
"AZURE_SPEECH_REGION",
"PLAY_HT_API_KEY",
"PLAY_HT_USER_ID",
"ELEVEN_LABS_API_KEY",
# todo add openai tsss
# todo add 11labs
]
Expand Down Expand Up @@ -338,6 +339,7 @@ def get_account_voice_settings(
AZURE_SPEECH_KEY=config.get("AZURE_SPEECH_KEY"),
AZURE_SPEECH_REGION=config.get("AZURE_SPEECH_REGION"),
PLAY_HT_API_KEY=config.get("PLAY_HT_API_KEY"),
ELEVEN_LABS_API_KEY=config.get("ELEVEN_LABS_API_KEY"),
PLAY_HT_USER_ID=config.get("PLAY_HT_USER_ID"),
)

Expand Down
1 change: 1 addition & 0 deletions apps/server/typings/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ class AccountVoiceSettings(BaseModel):
AZURE_SPEECH_REGION: Optional[str]
PLAY_HT_API_KEY: Optional[str]
PLAY_HT_USER_ID: Optional[str]
ELEVEN_LABS_API_KEY: Optional[str]
2 changes: 2 additions & 0 deletions apps/ui/src/Route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import CreateCampaignModal from 'modals/CreateCampaignModal'
import EditCampaignModal from 'modals/EditCampaignModal'
import CreateScheduleModal from 'modals/CreateScheduleModal'
import EditScheduleModal from 'modals/EditScheduleModal'
import VoiceOptionsModal from 'modals/VoiceOptionsModal'

const Route = () => {
const { loading } = useContext(AuthContext)
Expand Down Expand Up @@ -443,6 +444,7 @@ const Route = () => {
<EditCampaignModal />
<CreateScheduleModal />
<EditScheduleModal />
<VoiceOptionsModal />
<TwilioPhoneNumberSidConfirmationModal />

<CommandMenu
Expand Down
61 changes: 36 additions & 25 deletions apps/ui/src/components/AudioPlayer/AudioPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,30 +62,34 @@ const AudioPlayer = ({
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
}

useEffect(() => {
getDuration(audioUrl, function (duration: number) {
setDuration(duration)
})
}, [])

const getDuration = function (url: string, next: any) {
const _player = new Audio(url)
_player.addEventListener(
'durationchange',
function (e) {
if (this.duration != Infinity) {
const duration = this.duration
_player.remove()
next(duration)
}
},
false,
)
_player.load()
_player.currentTime = 24 * 60 * 60 //fake big time
_player.volume = 0
_player.play()
//waiting...
// useEffect(() => {
// getDuration(audioUrl, function (duration: number) {
// setDuration(duration)
// })
// }, [])

// const getDuration = function (url: string, next: any) {
// const _player = new Audio(url)
// _player.addEventListener(
// 'durationchange',
// function (e) {
// if (this.duration != Infinity) {
// const duration = this.duration
// _player.remove()
// next(duration)
// }
// },
// false,
// )
// _player.load()
// _player.currentTime = 24 * 60 * 60 //fake big time
// _player.volume = 0
// _player.play()
// //waiting...
// }

const handleMetadata = (e: any) => {
setDuration(e.target.duration)
}

return (
Expand All @@ -101,7 +105,12 @@ const AudioPlayer = ({
hasClose={onCloseClick ? true : false}
>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<audio ref={audioRef} src={audioUrl} onTimeUpdate={handleTimeUpdate} />
<audio
ref={audioRef}
src={audioUrl}
onTimeUpdate={handleTimeUpdate}
onLoadedMetadata={handleMetadata}
/>

<StyledTimeIndicator>
<span>{formatTime(currentTime)}</span> / <span>{formatTime(duration)}</span>
Expand Down Expand Up @@ -175,6 +184,8 @@ export const StyledTimeIndicator = styled.div`
opacity: 0.4;
color: #000;
font-size: 14px;
`
const StyledButton = styled.button`
width: 30px;
Expand Down
125 changes: 125 additions & 0 deletions apps/ui/src/modals/VoiceOptionsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import withRenderModal from 'hocs/withRenderModal'

import Modal from 'share-ui/components/Modal/Modal'
import { useModal } from 'hooks'
import styled from 'styled-components'

import { StyledModalBody } from './IntegrationListModal'
import { StyledSectionWrapper } from 'pages/Home/homeStyle.css'
import { ButtonTertiary } from 'components/Button/Button'

import AudioPlayer from 'components/AudioPlayer'

import Table from 'components/Table/Table'
import { useState } from 'react'
import { t } from 'i18next'
import { StyledTableButtons } from 'plugins/contact/pages/Group/Groups'

import TextField from 'share-ui/components/TextField/TextField'

type VoiceOptionsModalProps = {
data: {
formik: any
voiceList: any
}
}

const VoiceOptionsModal = ({ data }: VoiceOptionsModalProps) => {
const [searchText, setSearchText] = useState('')

const { closeModal } = useModal()

const { formik, voiceList } = data

const column = [
{
Header: 'Name',
accessor: 'name',
},
{
Header: 'Sample',
accessor: 'sample',
Cell: ({ cell }: any) => {
return <>{cell.value ? <AudioPlayer audioUrl={cell.value} /> : <span>-</span>}</>
},
minWidth: 80,
width: 80,
},
{
Header: 'Language',
accessor: 'language',
minWidth: 60,
width: 60,
},
{
Header: 'Gender',
accessor: 'gender',
minWidth: 60,
width: 60,
},
{
Header: 'Actions',
accessor: 'id',
minWidth: 60,
width: 60,
Cell: ({ cell }: any) => {
return (
<StyledTableButtons>
<ButtonTertiary
onClick={() => {
formik.setFieldValue('agent_voice_id', cell.value)
closeModal('voice-options-modal')
}}
size={'small'}
>
{t('add')}
</ButtonTertiary>
</StyledTableButtons>
)
},
},
]

const filteredData = voiceList?.filter((row: { name: string; phone: string }) => {
const includesSearchText = row.name.toLowerCase().includes(searchText.toLowerCase())

return includesSearchText
})

return (
<>
<Modal onClose={() => closeModal('voice-options-modal')} show backgroundColor='light'>
<StyledModalBody>
<StyledSectionWrapper>
<StyledTableWrapper>
<TextField
placeholder='Search contact'
value={searchText}
onChange={(value: string) => setSearchText(value || '')}
/>

<Table
columns={column}
data={filteredData || []}
// setPage={setPage}
// page={page}
// totalPages={10}
// isLoading={optionsLoading}
/>
</StyledTableWrapper>
</StyledSectionWrapper>
</StyledModalBody>
</Modal>
</>
)
}

export default withRenderModal('voice-options-modal')(VoiceOptionsModal)

const StyledTableWrapper = styled.div`
padding: 10px;
width: 100%;
height: calc(100% - 80px);
margin-top: 30px;
`
Loading

0 comments on commit 88c7e97

Please sign in to comment.