mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2026-07-24 13:02:21 +00:00
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
This commit is contained in:
@@ -318,8 +318,10 @@ export interface Application {
|
||||
speech_synthesis_voice: null | string;
|
||||
speech_synthesis_vendor: null | Lowercase<Vendor>;
|
||||
speech_synthesis_language: null | string;
|
||||
speech_synthesis_label: null | string;
|
||||
speech_recognizer_vendor: null | Lowercase<Vendor>;
|
||||
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 {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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<SpeechCredential[]>(apiUrl);
|
||||
const [softTtsVendor, setSoftTtsVendor] = useState<VendorOptions[]>(vendors);
|
||||
const [softSttVendor, setSoftSttVendor] = useState<VendorOptions[]>(vendors);
|
||||
const [recogLabel, setRecogLabel] = useState("");
|
||||
const [ttsLabelOptions, setTtsLabelOptions] = useState<LabelOptions[]>([]);
|
||||
const [sttLabelOptions, setSttLabelOptions] = useState<LabelOptions[]>([]);
|
||||
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 && (
|
||||
<>
|
||||
<label htmlFor="synthesis_label">Label</label>
|
||||
<Selector
|
||||
id="systhesis_label"
|
||||
name="systhesis_label"
|
||||
value={synthLabel}
|
||||
options={ttsLabelOptions}
|
||||
onChange={(e) => {
|
||||
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 && (
|
||||
<>
|
||||
<label htmlFor="recog_label">Label</label>
|
||||
<Selector
|
||||
id="recog_label"
|
||||
name="recog_label"
|
||||
value={recogLabel}
|
||||
options={sttLabelOptions}
|
||||
onChange={(e) => {
|
||||
setRecogLabel(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{recogVendor &&
|
||||
!recogVendor.toString().startsWith(VENDOR_CUSTOM) &&
|
||||
recogLang && (
|
||||
|
||||
@@ -19,7 +19,11 @@ export const DeleteSpeechService = ({
|
||||
return (
|
||||
<Modal handleCancel={handleCancel} handleSubmit={handleSubmit}>
|
||||
<P>
|
||||
Are you sure you want to delete the <strong>{credential.vendor}</strong>{" "}
|
||||
Are you sure you want to delete the{" "}
|
||||
<strong>
|
||||
{credential.vendor}
|
||||
{credential.label ? ` (${credential.label})` : ""}
|
||||
</strong>{" "}
|
||||
speech service?
|
||||
</P>
|
||||
</Modal>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label htmlFor="speech_label">
|
||||
Label
|
||||
<Tooltip text="Assign a label only if you need to create multiple speech services from the same vendor. Then use the label in your application to specify which service to use.">
|
||||
{" "}
|
||||
</Tooltip>
|
||||
</label>
|
||||
<input
|
||||
id="speech_label"
|
||||
type="text"
|
||||
name="speech_label"
|
||||
value={label}
|
||||
disabled={credential ? true : false}
|
||||
onChange={(e) => setLabel(e.target.value)}
|
||||
/>
|
||||
</fieldset>
|
||||
{vendor && (
|
||||
<fieldset>
|
||||
{vendor !== VENDOR_DEEPGRAM &&
|
||||
|
||||
@@ -76,7 +76,11 @@ export const SpeechServices = () => {
|
||||
refetch();
|
||||
toastSuccess(
|
||||
<>
|
||||
Deleted speech service <strong>{credential.vendor}</strong>
|
||||
Deleted speech service{" "}
|
||||
<strong>
|
||||
{credential.vendor}
|
||||
{credential.label ? ` (${credential.label})` : ""}
|
||||
</strong>{" "}
|
||||
</>
|
||||
);
|
||||
})
|
||||
@@ -195,6 +199,14 @@ export const SpeechServices = () => {
|
||||
<div>
|
||||
<CredentialStatus cred={credential} />
|
||||
</div>
|
||||
{credential.label && (
|
||||
<div>
|
||||
<div className="i txt--teal">
|
||||
<Icons.Tag />
|
||||
<span>{credential.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ScopedAccess
|
||||
|
||||
Vendored
+5
@@ -15,6 +15,11 @@ export interface VendorOptions {
|
||||
value: Lowercase<Vendor>;
|
||||
}
|
||||
|
||||
export interface LabelOptions {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Region {
|
||||
name: string;
|
||||
value: string;
|
||||
|
||||
Reference in New Issue
Block a user