From 843d1eda1eaec36bdd56207be8d6031e5295b599 Mon Sep 17 00:00:00 2001 From: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com> Date: Tue, 15 Aug 2023 20:00:00 +0700 Subject: [PATCH] feat support multiple speech credential with different labels but same vendor (#305) * feat support multiple speech credential with different labels but same vendor * fix * fix review comment * fix review comment * fix label tooltip * fixed --- src/api/types.ts | 3 + src/components/icons.ts | 2 + .../internal/views/applications/form.tsx | 86 ++++++++++++++++++- .../internal/views/speech-services/delete.tsx | 6 +- .../internal/views/speech-services/form.tsx | 23 ++++- .../internal/views/speech-services/index.tsx | 14 ++- src/vendor/types.ts | 5 ++ 7 files changed, 133 insertions(+), 6 deletions(-) diff --git a/src/api/types.ts b/src/api/types.ts index 7f23f2e..915570d 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -318,8 +318,10 @@ export interface Application { speech_synthesis_voice: null | string; speech_synthesis_vendor: null | Lowercase; speech_synthesis_language: null | string; + speech_synthesis_label: null | string; speech_recognizer_vendor: null | Lowercase; speech_recognizer_language: null | string; + speech_recognizer_label: null | string; record_all_calls: number; } @@ -391,6 +393,7 @@ export interface SpeechCredential { auth_token: null | string; custom_stt_url: null | string; custom_tts_url: null | string; + label: null | string; } export interface Alert { diff --git a/src/components/icons.ts b/src/components/icons.ts index f570d8e..68082dd 100644 --- a/src/components/icons.ts +++ b/src/components/icons.ts @@ -50,6 +50,7 @@ import { Smartphone, Youtube, Mail, + Tag, } from "react-feather"; import type { Icon } from "react-feather"; @@ -110,4 +111,5 @@ export const Icons: IconMap = { Smartphone, Youtube, Mail, + Tag, }; diff --git a/src/containers/internal/views/applications/form.tsx b/src/containers/internal/views/applications/form.tsx index e4e4fca..f2d3121 100644 --- a/src/containers/internal/views/applications/form.tsx +++ b/src/containers/internal/views/applications/form.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { Button, ButtonGroup, MS } from "@jambonz/ui-kit"; import { Link, useNavigate } from "react-router-dom"; @@ -46,6 +46,7 @@ import type { VoiceLanguage, Language, VendorOptions, + LabelOptions, } from "src/vendor/types"; import type { @@ -100,6 +101,10 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => { const [credentials] = useApiData(apiUrl); const [softTtsVendor, setSoftTtsVendor] = useState(vendors); const [softSttVendor, setSoftSttVendor] = useState(vendors); + const [recogLabel, setRecogLabel] = useState(""); + const [ttsLabelOptions, setTtsLabelOptions] = useState([]); + const [sttLabelOptions, setSttLabelOptions] = useState([]); + const [synthLabel, setSynthLabel] = useState(""); const [recordAllCalls, setRecordAllCalls] = useState(false); /** This lets us map and render the same UI for each... */ @@ -180,9 +185,11 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => { call_status_hook: statusWebhook || null, speech_synthesis_vendor: synthVendor || null, speech_synthesis_language: synthLang || null, + speech_synthesis_label: synthLabel || null, speech_synthesis_voice: synthVoice || null, speech_recognizer_vendor: recogVendor || null, speech_recognizer_language: recogLang || null, + speech_recognizer_label: recogLabel || null, record_all_calls: recordAllCalls ? 1 : 0, }; @@ -211,7 +218,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => { } }; - useEffect(() => { + useMemo(() => { if (credentials && hasLength(credentials)) { const v = credentials .filter((tv) => tv.vendor.startsWith(VENDOR_CUSTOM) && tv.use_for_tts) @@ -236,8 +243,47 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => { }) ); setSoftSttVendor(vendors.concat(v2)); + + const noneLabelObject = { + name: "None", + value: null, + }; + + let labels = credentials + .filter( + (c) => + c.label && + c.vendor === synthVendor && + c.account_sid === accountSid && + c.use_for_tts + ) + .map((c) => + Object.assign({ + name: c.label, + value: c.label, + }) + ); + + setTtsLabelOptions([noneLabelObject, ...labels]); + + labels = credentials + .filter( + (c) => + c.label && + c.vendor === recogVendor && + c.account_sid === accountSid && + c.use_for_stt + ) + .map((c) => + Object.assign({ + name: c.label, + value: c.label, + }) + ); + + setSttLabelOptions([noneLabelObject, ...labels]); } - }, [credentials]); + }, [credentials, synthVendor, recogVendor]); useEffect(() => { if (accountSid) { @@ -321,6 +367,12 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => { if (application.data.speech_recognizer_language) setRecogLang(application.data.speech_recognizer_language); + if (application.data.speech_synthesis_label) { + setSynthLabel(application.data.speech_synthesis_label); + } + if (application.data.speech_recognizer_label) { + setRecogLabel(application.data.speech_recognizer_label); + } } }, [application]); @@ -501,6 +553,20 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => { setSynthVoice(newLang!.voices[0].value); }} /> + {hasLength(ttsLabelOptions) && ttsLabelOptions.length > 1 && ( + <> + + { + setSynthLabel(e.target.value); + }} + /> + + )} {synthVendor && !synthVendor.toString().startsWith(VENDOR_CUSTOM) && synthLang && ( @@ -621,6 +687,20 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => { } }} /> + {hasLength(sttLabelOptions) && sttLabelOptions.length > 1 && ( + <> + + { + setRecogLabel(e.target.value); + }} + /> + + )} {recogVendor && !recogVendor.toString().startsWith(VENDOR_CUSTOM) && recogLang && ( diff --git a/src/containers/internal/views/speech-services/delete.tsx b/src/containers/internal/views/speech-services/delete.tsx index 7bc390f..7e7f879 100644 --- a/src/containers/internal/views/speech-services/delete.tsx +++ b/src/containers/internal/views/speech-services/delete.tsx @@ -19,7 +19,11 @@ export const DeleteSpeechService = ({ return (

- Are you sure you want to delete the {credential.vendor}{" "} + Are you sure you want to delete the{" "} + + {credential.vendor} + {credential.label ? ` (${credential.label})` : ""} + {" "} speech service?

diff --git a/src/containers/internal/views/speech-services/form.tsx b/src/containers/internal/views/speech-services/form.tsx index 656edd9..c0cd30f 100644 --- a/src/containers/internal/views/speech-services/form.tsx +++ b/src/containers/internal/views/speech-services/form.tsx @@ -3,7 +3,7 @@ import { Button, ButtonGroup, MS } from "@jambonz/ui-kit"; import { Link, useNavigate } from "react-router-dom"; import { ROUTE_INTERNAL_SPEECH } from "src/router/routes"; -import { Section } from "src/components"; +import { Section, Tooltip } from "src/components"; import { FileUpload, Selector, @@ -99,6 +99,7 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => { const [onPremNuanceSttCheck, setOnPremNuanceSttCheck] = useState(false); const [tmpOnPremNuanceSttUrl, setTmpOnPremNuanceSttUrl] = useState(""); const [onPremNuanceSttUrl, setOnPremNuanceSttUrl] = useState(""); + const [label, setLabel] = useState(""); const handleFile = (file: File) => { const handleError = () => { @@ -143,6 +144,7 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => { service_provider_sid: currentServiceProvider.service_provider_sid, use_for_tts: ttsCheck ? 1 : 0, use_for_stt: sttCheck ? 1 : 0, + label: label || null, ...(vendor === VENDOR_AWS && { aws_region: region || null, }), @@ -347,6 +349,9 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => { setTmpCustomVendorSttUrl(credential.data.custom_stt_url || ""); setCustomVendorTtsUrl(credential.data.custom_tts_url || ""); setTmpCustomVendorTtsUrl(credential.data.custom_tts_url || ""); + if (credential.data.label) { + setLabel(credential.data.label); + } } }, [credential]); @@ -416,6 +421,22 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => { disabled={credential ? true : false} /> +
+ + setLabel(e.target.value)} + /> +
{vendor && (
{vendor !== VENDOR_DEEPGRAM && diff --git a/src/containers/internal/views/speech-services/index.tsx b/src/containers/internal/views/speech-services/index.tsx index 33f946e..bddbbfc 100644 --- a/src/containers/internal/views/speech-services/index.tsx +++ b/src/containers/internal/views/speech-services/index.tsx @@ -76,7 +76,11 @@ export const SpeechServices = () => { refetch(); toastSuccess( <> - Deleted speech service {credential.vendor} + Deleted speech service{" "} + + {credential.vendor} + {credential.label ? ` (${credential.label})` : ""} + {" "} ); }) @@ -195,6 +199,14 @@ export const SpeechServices = () => {
+ {credential.label && ( +
+
+ + {credential.label} +
+
+ )} ; } +export interface LabelOptions { + name: string; + value: string; +} + export interface Region { name: string; value: string;