Compare commits

..

13 Commits

Author SHA1 Message Date
EgleH
aba8b2be3a logout with one click (#223)
Co-authored-by: eglehelms <e.helms@cognigy.com>
2023-03-30 07:44:19 -04:00
EgleH
f4d7880ab7 Add Logout call to signout (#221)
* Add Logout call to signout

* clean local storage even on error

---------

Co-authored-by: eglehelms <e.helms@cognigy.com>
2023-03-29 08:54:17 -04:00
Dave Horton
7f93489580 bump version 2023-03-28 14:15:26 -04:00
Dave Horton
19fafdc908 minor label change 2023-03-25 12:13:13 -04:00
Hoan Luu Huu
a165bfc4d6 feat: onpremise nuance (#220)
Co-authored-by: Quan HL <quanluuhoang8@gmail.com>
2023-03-25 11:21:14 -04:00
Hoan Luu Huu
e26d9b95cb fix: edit application does not clear webhook user/pass when checkbox is uncheck (#217)
Co-authored-by: Quan HL <quanluuhoang8@gmail.com>
2023-03-21 20:14:16 -04:00
Hoan Luu Huu
e425d825bc feat: custom Vendor (#215)
* feat: custom Vendor

* feat: custom Vendor

* fix: application with custom tts/stt vendor

* fix custom speech name when editing

* fix: all comments

* fix: remove custom in the list and show extra (custom)

* fix: prettier and application sythesizer selector

* fix: addd VITE_DISABLE_CUSTOM_SPEECH

---------

Co-authored-by: Quan HL <quanluuhoang8@gmail.com>
2023-03-14 08:45:06 -04:00
EgleH
a8d28da221 add soniox as speech provider (#211)
Co-authored-by: EgleHelms <e.helms@cognigy.com>
2023-03-03 18:53:46 -05:00
EgleH
e3855e83f7 conditional required causing issue with focusable fields (#210)
Co-authored-by: EgleHelms <e.helms@cognigy.com>
2023-02-23 12:18:01 -05:00
Dave Horton
446b6e76e2 update dockerfile 2023-02-23 08:21:09 -05:00
EgleH
0b55cdcf85 add env VITE_APP_DISABLE_DEFAULT_TRUNK_ROUTING (#209)
Co-authored-by: EgleHelms <e.helms@cognigy.com>
2023-02-22 14:05:56 -05:00
Snyk bot
f1743a9129 fix: Dockerfile to reduce vulnerabilities (#208)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE316-OPENSSL-3314624
- https://snyk.io/vuln/SNYK-ALPINE316-OPENSSL-3314641
- https://snyk.io/vuln/SNYK-ALPINE316-OPENSSL-3314641
- https://snyk.io/vuln/SNYK-ALPINE316-OPENSSL-3314643
- https://snyk.io/vuln/SNYK-ALPINE316-OPENSSL-3314643
2023-02-22 07:32:42 -05:00
EgleH
ec46121696 upgrade node image (#206)
Co-authored-by: EgleHelms <e.helms@cognigy.com>
2023-02-20 10:31:07 -05:00
17 changed files with 599 additions and 154 deletions

5
.env
View File

@@ -2,4 +2,7 @@ VITE_API_BASE_URL=http://127.0.0.1:3000/v1
VITE_DEV_BASE_URL=http://127.0.0.1:3000/v1
## enables choosing units and lisenced account call limits
# VITE_APP_ENABLE_ACCOUNT_LIMITS_ALL=true
# VITE_APP_ENABLE_ACCOUNT_LIMITS_ALL=true
# disables controls for default application routing to carrier for SP and account level users
#VITE_APP_DISABLE_DEFAULT_TRUNK_ROUTING=true

View File

@@ -1,4 +1,4 @@
FROM node:18.14.0-alpine3.16 as builder
FROM node:18.14.1-alpine3.16 as builder
RUN apk update && apk add --no-cache python3 make g++
COPY . /opt/app
WORKDIR /opt/app/
@@ -6,7 +6,7 @@ RUN npm install
RUN npm run build
RUN npm prune
FROM node:18.9.0-alpine as webapp
FROM node:18.14.1-alpine as webapp
RUN apk add curl
WORKDIR /opt/app
COPY . /opt/app

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "jambonz-webapp",
"version": "v1.0.0",
"version": "v0.8.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "jambonz-webapp",
"version": "v1.0.0",
"version": "v0.8.2",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "jambonz-webapp",
"description": "A simple provisioning web app for jambonz",
"version": "v1.0.0",
"version": "v0.8.2",
"license": "MIT",
"type": "module",
"engines": {

View File

@@ -28,6 +28,11 @@ export const API_BASE_URL =
/** Serves mock API responses from a local dev API server */
export const DEV_BASE_URL = import.meta.env.VITE_DEV_BASE_URL;
/** Disable custom speech vendor*/
export const DISABLE_CUSTOM_SPEECH: boolean = JSON.parse(
import.meta.env.VITE_DISABLE_CUSTOM_SPEECH || "false"
);
/** TCP Max Port */
export const TCP_MAX_PORT = 65535;
@@ -184,6 +189,7 @@ export const CRED_NOT_TESTED = "not tested";
/** API base paths */
export const API_LOGIN = `${API_BASE_URL}/login`;
export const API_LOGOUT = `${API_BASE_URL}/logout`;
export const API_SBCS = `${API_BASE_URL}/Sbcs`;
export const API_USERS = `${API_BASE_URL}/Users`;
export const API_API_KEYS = `${API_BASE_URL}/ApiKeys`;

View File

@@ -18,6 +18,7 @@ import {
API_SIP_GATEWAY,
API_PASSWORD_SETTINGS,
USER_ACCOUNT,
API_LOGOUT,
} from "./constants";
import { ROUTE_LOGIN } from "src/router/routes";
import {
@@ -233,6 +234,10 @@ export const postLogin = (payload: UserLoginPayload) => {
});
};
export const postLogout = () => {
return postFetch<undefined>(API_LOGOUT);
};
/** Named wrappers for `postFetch` */
export const postServiceProviders = (payload: Partial<ServiceProvider>) => {

View File

@@ -303,12 +303,17 @@ export interface SpeechCredential {
custom_stt_endpoint: null | string;
client_id: null | string;
secret: null | string;
nuance_tts_uri: null | string;
nuance_stt_uri: null | string;
tts_api_key: null | string;
tts_region: null | string;
stt_api_key: null | string;
stt_region: null | string;
instance_id: null | string;
riva_server_uri: null | string;
auth_token: null | string;
custom_stt_url: null | string;
custom_tts_url: null | string;
}
export interface Alert {

View File

@@ -418,11 +418,6 @@ export const AccountForm = ({ apps, limits, account }: AccountFormProps) => {
username: e.target.value,
});
}}
required={
webhook.stateVal.password && !webhook.stateVal.username
? true
: false
}
/>
<label htmlFor={`${webhook.prefix}_password`}>
Password
@@ -438,11 +433,6 @@ export const AccountForm = ({ apps, limits, account }: AccountFormProps) => {
password: e.target.value,
});
}}
required={
webhook.stateVal.username && !webhook.stateVal.password
? true
: false
}
/>
</Checkzone>
</div>

View File

@@ -20,6 +20,8 @@ import {
VENDOR_WELLSAID,
useSpeechVendors,
VENDOR_DEEPGRAM,
VENDOR_SONIOX,
VENDOR_CUSTOM,
} from "src/vendor";
import {
postApplication,
@@ -39,6 +41,7 @@ import type {
Voice,
VoiceLanguage,
Language,
VendorOptions,
} from "src/vendor/types";
import type {
@@ -47,9 +50,10 @@ import type {
Application,
WebhookMethod,
UseApiDataMap,
SpeechCredential,
} from "src/api/types";
import { MSG_REQUIRED_FIELDS, MSG_WEBHOOK_FIELDS } from "src/constants";
import { isUserAccountScope, useRedirect } from "src/utils";
import { hasLength, isUserAccountScope, useRedirect } from "src/utils";
import { setAccountFilter, setLocation } from "src/store/localStore";
type ApplicationFormProps = {
@@ -68,11 +72,17 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
const [initialApplicationJson, setInitialApplicationJson] = useState(false);
const [accountSid, setAccountSid] = useState("");
const [callWebhook, setCallWebhook] = useState<WebHook>(DEFAULT_WEBHOOK);
const [tmpCallWebhook, setTmpCallWebhook] =
useState<WebHook>(DEFAULT_WEBHOOK);
const [initialCallWebhook, setInitialCallWebhook] = useState(false);
const [statusWebhook, setStatusWebhook] = useState<WebHook>(DEFAULT_WEBHOOK);
const [tmpStatusWebhook, setTmpStatusWebhook] =
useState<WebHook>(DEFAULT_WEBHOOK);
const [initialStatusWebhook, setInitialStatusWebhook] = useState(false);
const [messageWebhook, setMessageWebhook] =
useState<WebHook>(DEFAULT_WEBHOOK);
const [tmpMessageWebhook, setTmpMessageWebhook] =
useState<WebHook>(DEFAULT_WEBHOOK);
const [initialMessageWebhook, setInitialMessageWebhook] = useState(false);
const [synthVendor, setSynthVendor] =
useState<keyof SynthesisVendors>(VENDOR_GOOGLE);
@@ -82,6 +92,10 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
useState<keyof RecognizerVendors>(VENDOR_GOOGLE);
const [recogLang, setRecogLang] = useState(LANG_EN_US);
const [message, setMessage] = useState("");
const [apiUrl, setApiUrl] = useState("");
const [credentials] = useApiData<SpeechCredential[]>(apiUrl);
const [softTtsVendor, setSoftTtsVendor] = useState<VendorOptions[]>(vendors);
const [softSttVendor, setSoftSttVendor] = useState<VendorOptions[]>(vendors);
/** This lets us map and render the same UI for each... */
const webhooks = [
@@ -89,7 +103,9 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
label: "Calling",
prefix: "call_webhook",
stateVal: callWebhook,
tmpStateVal: tmpCallWebhook,
stateSet: setCallWebhook,
tmpStateSet: setTmpCallWebhook,
initialCheck: initialCallWebhook,
required: true,
},
@@ -97,7 +113,9 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
label: "Call status",
prefix: "status_webhook",
stateVal: statusWebhook,
tmpStateVal: tmpStatusWebhook,
stateSet: setStatusWebhook,
tmpStateSet: setTmpStatusWebhook,
initialCheck: initialStatusWebhook,
required: true,
},
@@ -105,7 +123,9 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
label: "Messaging",
prefix: "message_webhook",
stateVal: messageWebhook,
tmpStateVal: tmpMessageWebhook,
stateSet: setMessageWebhook,
tmpStateSet: setTmpMessageWebhook,
initialCheck: initialMessageWebhook,
required: false,
},
@@ -185,6 +205,40 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
}
};
useEffect(() => {
if (credentials && hasLength(credentials)) {
const v = credentials
.filter((tv) => tv.vendor.startsWith(VENDOR_CUSTOM) && tv.use_for_tts)
.map((tv) =>
Object.assign({
name:
tv.vendor.substring(VENDOR_CUSTOM.length + 1) +
` (${VENDOR_CUSTOM})`,
value: tv.vendor,
})
);
setSoftTtsVendor(vendors.concat(v));
const v2 = credentials
.filter((tv) => tv.vendor.startsWith(VENDOR_CUSTOM) && tv.use_for_stt)
.map((tv) =>
Object.assign({
name:
tv.vendor.substring(VENDOR_CUSTOM.length + 1) +
` (${VENDOR_CUSTOM})`,
value: tv.vendor,
})
);
setSoftSttVendor(vendors.concat(v2));
}
}, [credentials]);
useEffect(() => {
if (accountSid) {
setApiUrl(`Accounts/${accountSid}/SpeechCredentials`);
}
}, [accountSid]);
useEffect(() => {
setLocation();
if (application && application.data) {
@@ -200,6 +254,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
if (application.data.call_hook) {
setCallWebhook(application.data.call_hook);
setTmpCallWebhook(application.data.call_hook);
if (
application.data.call_hook.username ||
@@ -211,6 +266,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
if (application.data.call_status_hook) {
setStatusWebhook(application.data.call_status_hook);
setTmpStatusWebhook(application.data.call_status_hook);
if (
application.data.call_status_hook.username ||
@@ -222,6 +278,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
if (application.data.messaging_hook) {
setMessageWebhook(application.data.messaging_hook);
setTmpMessageWebhook(application.data.messaging_hook);
if (
application.data.messaging_hook.username ||
@@ -341,6 +398,18 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
name={webhook.prefix}
label="Use HTTP basic authentication"
initialCheck={webhook.initialCheck}
handleChecked={(e) => {
if (e.target.checked) {
webhook.stateSet(webhook.tmpStateVal);
} else {
webhook.tmpStateSet(webhook.stateVal);
webhook.stateSet({
...webhook.stateVal,
username: "",
password: "",
});
}
}}
>
<MS>{MSG_WEBHOOK_FIELDS}</MS>
<label htmlFor={`${webhook.prefix}_username`}>Username</label>
@@ -356,13 +425,6 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
username: e.target.value,
});
}}
required={
webhook.required &&
!webhook.stateVal.username &&
webhook.stateVal.password
? true
: false
}
/>
<label htmlFor={`${webhook.prefix}_password`}>Password</label>
<Passwd
@@ -376,13 +438,6 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
password: e.target.value,
});
}}
required={
webhook.required &&
webhook.stateVal.username &&
!webhook.stateVal.password
? true
: false
}
/>
</Checkzone>
</fieldset>
@@ -395,13 +450,22 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
id="synthesis_vendor"
name="synthesis_vendor"
value={synthVendor}
options={vendors.filter(
(vendor) => vendor.value != VENDOR_DEEPGRAM
options={softTtsVendor.filter(
(vendor) =>
vendor.value != VENDOR_DEEPGRAM &&
vendor.value != VENDOR_SONIOX &&
vendor.value !== VENDOR_CUSTOM
)}
onChange={(e) => {
const vendor = e.target.value as keyof SynthesisVendors;
setSynthVendor(vendor);
/** When Custom Vendor is used, user you have to input the lange and voice. */
if (vendor.toString().startsWith(VENDOR_CUSTOM)) {
setSynthVoice("");
return;
}
/** When using Google and en-US, ensure "Standard-C" is used as default */
if (
e.target.value === VENDOR_GOOGLE &&
@@ -430,55 +494,88 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
setSynthVoice(newLang!.voices[0].value);
}}
/>
{synthVendor && synthLang && (
<>
<label htmlFor="synthesis_lang">Language</label>
<Selector
id="synthesis_lang"
name="synthesis_lang"
value={synthLang}
options={synthesis[synthVendor as keyof SynthesisVendors].map(
(lang: VoiceLanguage) => ({
{synthVendor &&
!synthVendor.toString().startsWith(VENDOR_CUSTOM) &&
synthLang && (
<>
<label htmlFor="synthesis_lang">Language</label>
<Selector
id="synthesis_lang"
name="synthesis_lang"
value={synthLang}
options={synthesis[
synthVendor as keyof SynthesisVendors
].map((lang: VoiceLanguage) => ({
name: lang.name,
value: lang.code,
})
)}
onChange={(e) => {
const language = e.target.value;
setSynthLang(language);
}))}
onChange={(e) => {
const language = e.target.value;
setSynthLang(language);
/** When using Google and en-US, ensure "Standard-C" is used as default */
if (
synthVendor === VENDOR_GOOGLE &&
language === LANG_EN_US
) {
setSynthVoice(LANG_EN_US_STANDARD_C);
return;
/** When using Google and en-US, ensure "Standard-C" is used as default */
if (
synthVendor === VENDOR_GOOGLE &&
language === LANG_EN_US
) {
setSynthVoice(LANG_EN_US_STANDARD_C);
return;
}
const newLang = synthesis[
synthVendor as keyof SynthesisVendors
].find((lang) => lang.code === language);
setSynthVoice(newLang!.voices[0].value);
}}
/>
<label htmlFor="synthesis_voice">Voice</label>
<Selector
id="synthesis_voice"
name="synthesis_voice"
value={synthVoice}
options={
synthesis[synthVendor as keyof SynthesisVendors]
.filter(
(lang: VoiceLanguage) => lang.code === synthLang
)
.flatMap((lang: VoiceLanguage) =>
lang.voices.map((voice: Voice) => ({
name: voice.name,
value: voice.value,
}))
) as Voice[]
}
const newLang = synthesis[
synthVendor as keyof SynthesisVendors
].find((lang) => lang.code === language);
setSynthVoice(newLang!.voices[0].value);
onChange={(e) => setSynthVoice(e.target.value)}
/>
</>
)}
{synthVendor.toString().startsWith(VENDOR_CUSTOM) && (
<>
<label htmlFor="custom_vendor_synthesis_lang">Language</label>
<input
id="custom_vendor_synthesis_lang"
type="text"
name="custom_vendor_synthesis_lang"
placeholder="Required"
required
value={synthLang}
onChange={(e) => {
setSynthLang(e.target.value);
}}
/>
<label htmlFor="synthesis_voice">Voice</label>
<Selector
id="synthesis_voice"
name="synthesis_voice"
<label htmlFor="custom_vendor_synthesis_voice">Voice</label>
<input
id="custom_vendor_synthesis_voice"
type="text"
name="custom_vendor_synthesis_voice"
placeholder="Required"
required
value={synthVoice}
options={
synthesis[synthVendor as keyof SynthesisVendors]
.filter((lang: VoiceLanguage) => lang.code === synthLang)
.flatMap((lang: VoiceLanguage) =>
lang.voices.map((voice: Voice) => ({
name: voice.name,
value: voice.value,
}))
) as Voice[]
}
onChange={(e) => setSynthVoice(e.target.value)}
onChange={(e) => {
setSynthVoice(e.target.value);
}}
/>
</>
)}
@@ -491,13 +588,18 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
id="recognizer_vendor"
name="recognizer_vendor"
value={recogVendor}
options={vendors.filter(
(vendor) => vendor.value != VENDOR_WELLSAID
options={softSttVendor.filter(
(vendor) =>
vendor.value != VENDOR_WELLSAID &&
vendor.value !== VENDOR_CUSTOM
)}
onChange={(e) => {
const vendor = e.target.value as keyof RecognizerVendors;
setRecogVendor(vendor);
/**When vendor is custom, Language is input by user */
if (vendor.toString() === VENDOR_CUSTOM) return;
/** Google and AWS have different language lists */
/** If the new language doesn't map then default to "en-US" */
const newLang = recognizers[vendor].find(
@@ -512,19 +614,37 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
}
}}
/>
{recogVendor && recogLang && (
{recogVendor &&
!recogVendor.toString().startsWith(VENDOR_CUSTOM) &&
recogLang && (
<>
<label htmlFor="recognizer_lang">Language</label>
<Selector
id="recognizer_lang"
name="recognizer_lang"
value={recogLang}
options={recognizers[
recogVendor as keyof RecognizerVendors
].map((lang: Language) => ({
name: lang.name,
value: lang.code,
}))}
onChange={(e) => {
setRecogLang(e.target.value);
}}
/>
</>
)}
{recogVendor.toString().startsWith(VENDOR_CUSTOM) && (
<>
<label htmlFor="recognizer_lang">Language</label>
<Selector
id="recognizer_lang"
name="recognizer_lang"
<label htmlFor="custom_vendor_recognizer_voice">Language</label>
<input
id="custom_vendor_recognizer_voice"
type="text"
name="custom_vendor_recognizer_voice"
placeholder="Required"
required
value={recogLang}
options={recognizers[
recogVendor as keyof RecognizerVendors
].map((lang: Language) => ({
name: lang.name,
value: lang.code,
}))}
onChange={(e) => {
setRecogLang(e.target.value);
}}

View File

@@ -45,6 +45,7 @@ import {
isUserAccountScope,
hasLength,
isValidPort,
disableDefaultTrunkRouting,
} from "src/utils";
import type {
@@ -725,18 +726,21 @@ export const CarrierForm = ({
: false
}
/>
{accountSid && hasLength(applications) && (
<>
<ApplicationSelect
label="Default Application"
defaultOption="None"
application={[applicationSid, setApplicationSid]}
applications={applications.filter(
(application) => application.account_sid === accountSid
)}
/>
</>
)}
{user &&
disableDefaultTrunkRouting(user?.scope) &&
accountSid &&
hasLength(applications) && (
<>
<ApplicationSelect
label="Default Application"
defaultOption="None"
application={[applicationSid, setApplicationSid]}
applications={applications.filter(
(application) => application.account_sid === accountSid
)}
/>
</>
)}
</fieldset>
<fieldset>
<Checkzone

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { Fragment, useEffect, useState } from "react";
import { Button, ButtonGroup, MS } from "@jambonz/ui-kit";
import { Link, useNavigate } from "react-router-dom";
@@ -9,6 +9,7 @@ import {
Selector,
Passwd,
AccountSelect,
Checkzone,
} from "src/components/forms";
import { toastError, toastSuccess, useSelectState } from "src/store";
import {
@@ -27,6 +28,8 @@ import {
VENDOR_DEEPGRAM,
VENDOR_IBM,
VENDOR_NVIDIA,
VENDOR_SONIOX,
VENDOR_CUSTOM,
} from "src/vendor";
import { MSG_REQUIRED_FIELDS } from "src/constants";
import {
@@ -40,6 +43,7 @@ import { CredentialStatus } from "./status";
import type { RegionVendors, GoogleServiceKey, Vendor } from "src/vendor/types";
import type { Account, SpeechCredential, UseApiDataMap } from "src/api/types";
import { setAccountFilter, setLocation } from "src/store/localStore";
import { DISABLE_CUSTOM_SPEECH } from "src/api/constants";
type SpeechServiceFormProps = {
credential?: UseApiDataMap<SpeechCredential>;
@@ -52,7 +56,9 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
const regions = useRegionVendors();
const [accounts] = useServiceProviderData<Account[]>("Accounts");
const [accountSid, setAccountSid] = useState("");
const [initialTtsCheck, setInitialTtsCheck] = useState(false);
const [ttsCheck, setTtsCheck] = useState(false);
const [initialSttCheck, setInitialSttCheck] = useState(false);
const [sttCheck, setSttCheck] = useState(false);
const [vendor, setVendor] = useState<Lowercase<Vendor>>(
"" as Lowercase<Vendor>
@@ -77,6 +83,22 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
const [customSttEndpoint, setCustomSttEndpoint] = useState("");
const [tmpCustomSttEndpoint, setTmpCustomSttEndpoint] = useState("");
const [rivaServerUri, setRivaServerUri] = useState("");
const [customVendorName, setCustomVendorName] = useState("");
const [customVendorAuthToken, setCustomVendorAuthToken] = useState("");
const [tmpCustomVendorTtsUrl, setTmpCustomVendorTtsUrl] = useState("");
const [customVendorTtsUrl, setCustomVendorTtsUrl] = useState("");
const [tmpCustomVendorSttUrl, setTmpCustomVendorSttUrl] = useState("");
const [customVendorSttUrl, setCustomVendorSttUrl] = useState("");
const [initialOnPremNuanceTtsCheck, setInitialOnPremNuanceTtsCheck] =
useState(false);
const [onPremNuanceTtsCheck, setOnPremNuanceTtsCheck] = useState(false);
const [onPremNuanceTtsUrl, setOnPremNuanceTtsUrl] = useState("");
const [tmpOnPremNuanceTtsUrl, setTmpOnPremNuanceTtsUrl] = useState("");
const [initialOnPremNuanceSttCheck, setInitialOnPremNuanceSttCheck] =
useState(false);
const [onPremNuanceSttCheck, setOnPremNuanceSttCheck] = useState(false);
const [tmpOnPremNuanceSttUrl, setTmpOnPremNuanceSttUrl] = useState("");
const [onPremNuanceSttUrl, setOnPremNuanceSttUrl] = useState("");
const handleFile = (file: File) => {
const handleError = () => {
@@ -141,6 +163,20 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
...(vendor === VENDOR_NVIDIA && {
riva_server_uri: rivaServerUri || null,
}),
...(vendor === VENDOR_CUSTOM && {
vendor: (vendor + ":" + customVendorName) as Lowercase<Vendor>,
use_for_tts: ttsCheck ? 1 : 0,
use_for_stt: sttCheck ? 1 : 0,
custom_tts_url: customVendorTtsUrl || null,
custom_stt_url: customVendorSttUrl || null,
auth_token: customVendorAuthToken || null,
}),
...(vendor === VENDOR_NUANCE && {
client_id: clientId || null,
secret: secretKey || null,
nuance_tts_uri: onPremNuanceTtsUrl || null,
nuance_stt_uri: onPremNuanceSttUrl || null,
}),
};
if (credential && credential.data) {
@@ -173,11 +209,10 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
api_key:
vendor === VENDOR_MICROSOFT ||
vendor === VENDOR_WELLSAID ||
vendor === VENDOR_DEEPGRAM
vendor === VENDOR_DEEPGRAM ||
vendor === VENDOR_SONIOX
? apiKey
: null,
client_id: vendor === VENDOR_NUANCE ? clientId : null,
secret: vendor === VENDOR_NUANCE ? secretKey : null,
riva_server_uri: vendor == VENDOR_NVIDIA ? rivaServerUri : null,
})
.then(() => {
@@ -196,7 +231,10 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
setLocation();
if (credential && credential.data) {
if (credential.data.vendor) {
setVendor(credential.data.vendor);
const v = credential.data.vendor.startsWith(VENDOR_CUSTOM)
? VENDOR_CUSTOM
: credential.data.vendor;
setVendor(v);
}
if (credential.data.account_sid) {
@@ -205,14 +243,18 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
if (credential.data.use_for_stt) {
setSttCheck(true);
setInitialSttCheck(true);
} else {
setSttCheck(false);
setInitialSttCheck(false);
}
if (credential.data.use_for_tts) {
setTtsCheck(true);
setInitialTtsCheck(true);
} else {
setTtsCheck(false);
setInitialTtsCheck(false);
}
if (credential.data.service_key) {
@@ -247,6 +289,24 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
setSecretKey(credential.data.secret);
}
if (credential.data.nuance_tts_uri) {
setOnPremNuanceTtsUrl(credential.data.nuance_tts_uri);
setInitialOnPremNuanceTtsCheck(true);
setOnPremNuanceTtsCheck(true);
} else {
setInitialOnPremNuanceTtsCheck(false);
setOnPremNuanceTtsCheck(false);
}
if (credential.data.nuance_stt_uri) {
setOnPremNuanceSttUrl(credential.data.nuance_stt_uri);
setInitialOnPremNuanceSttCheck(true);
setOnPremNuanceSttCheck(true);
} else {
setInitialOnPremNuanceSttCheck(false);
setOnPremNuanceSttCheck(false);
}
if (credential.data.tts_api_key) {
setTtsApiKey(credential.data.tts_api_key);
}
@@ -277,6 +337,16 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
setCustomSttEndpoint(credential.data.custom_stt_endpoint || "");
setTmpCustomTtsEndpoint(credential.data.custom_tts_endpoint || "");
setTmpCustomSttEndpoint(credential.data.custom_stt_endpoint || "");
setCustomVendorName(
credential.data.vendor.startsWith(VENDOR_CUSTOM)
? credential.data.vendor.substring(VENDOR_CUSTOM.length + 1)
: credential.data.vendor
);
setCustomVendorAuthToken(credential.data.auth_token || "");
setCustomVendorSttUrl(credential.data.custom_stt_url || "");
setTmpCustomVendorSttUrl(credential.data.custom_stt_url || "");
setCustomVendorTtsUrl(credential.data.custom_tts_url || "");
setTmpCustomVendorTtsUrl(credential.data.custom_tts_url || "");
}
}, [credential]);
@@ -305,7 +375,11 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
name: "Select a vendor",
value: "",
},
].concat(vendors)}
]
.concat(vendors)
.filter(
(v) => !DISABLE_CUSTOM_SPEECH || v.value !== VENDOR_CUSTOM
)}
onChange={(e) => {
setVendor(e.target.value as Lowercase<Vendor>);
setRegion("");
@@ -315,6 +389,23 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
disabled={credential ? true : false}
required
/>
{vendor === VENDOR_CUSTOM && (
<>
<label htmlFor="custom_vendor_name">
Name<span>*</span>
</label>
<input
id="custom_vendor_name"
required
type="text"
name="custom_vendor_name"
placeholder="Vendor Name"
value={customVendorName}
onChange={(e) => setCustomVendorName(e.target.value)}
disabled={credential ? true : false}
/>
</>
)}
</fieldset>
<fieldset>
<AccountSelect
@@ -327,19 +418,21 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
</fieldset>
{vendor && (
<fieldset>
{vendor !== VENDOR_DEEPGRAM && (
<label htmlFor="use_for_tts" className="chk">
<input
id="use_for_tts"
name="use_for_tts"
type="checkbox"
onChange={(e) => setTtsCheck(e.target.checked)}
defaultChecked={ttsCheck}
/>
<div>Use for text-to-speech</div>
</label>
)}
{vendor !== VENDOR_WELLSAID && (
{vendor !== VENDOR_DEEPGRAM &&
vendor !== VENDOR_SONIOX &&
vendor != VENDOR_CUSTOM && (
<label htmlFor="use_for_tts" className="chk">
<input
id="use_for_tts"
name="use_for_tts"
type="checkbox"
onChange={(e) => setTtsCheck(e.target.checked)}
defaultChecked={ttsCheck}
/>
<div>Use for text-to-speech</div>
</label>
)}
{vendor !== VENDOR_WELLSAID && vendor !== VENDOR_CUSTOM && (
<label htmlFor="use_for_stt" className="chk">
<input
id="use_for_stt"
@@ -351,6 +444,87 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
<div>Use for speech-to-text</div>
</label>
)}
{vendor === VENDOR_CUSTOM && (
<Fragment>
<Checkzone
hidden
name="custom_vendor_use_for_tts"
label="Use for text-to-speech"
initialCheck={initialTtsCheck}
handleChecked={(e) => {
setTtsCheck(e.target.checked);
if (!e.target.checked) {
setTmpCustomVendorTtsUrl(customVendorTtsUrl);
setCustomVendorTtsUrl("");
} else {
setCustomVendorTtsUrl(tmpCustomVendorTtsUrl);
}
}}
>
<label htmlFor="custom_vendor_use_for_tts">
TTS HTTP URL<span>*</span>
</label>
<input
id="custom_vendor_use_for_tts"
type="text"
name="custom_vendor_use_for_tts"
placeholder="Required"
required={ttsCheck}
value={customVendorTtsUrl}
onChange={(e) => {
setCustomVendorTtsUrl(e.target.value);
}}
/>
</Checkzone>
<Checkzone
hidden
name="custom_vendor_use_for_stt"
label="Use for speech-to-text"
initialCheck={initialSttCheck}
handleChecked={(e) => {
setSttCheck(e.target.checked);
if (!e.target.checked) {
setTmpCustomVendorSttUrl(customVendorSttUrl);
setCustomVendorSttUrl("");
} else {
setCustomVendorSttUrl(tmpCustomVendorSttUrl);
}
}}
>
<label htmlFor="custom_vendor_use_for_stt">
STT websocket URL<span>*</span>
</label>
<input
id="custom_vendor_use_for_stt"
type="text"
name="custom_vendor_use_for_stt"
placeholder="Required"
required={sttCheck}
value={customVendorSttUrl}
onChange={(e) => {
setCustomVendorSttUrl(e.target.value);
}}
/>
</Checkzone>
</Fragment>
)}
</fieldset>
)}
{vendor === VENDOR_CUSTOM && (
<fieldset>
<label htmlFor="custom_vendor_auth_token">
Authentication Token
</label>
<input
id="custom_vendor_auth_token"
type="text"
name="custom_vendor_auth_token"
placeholder="Authentication Token"
value={customVendorAuthToken}
onChange={(e) => setCustomVendorAuthToken(e.target.value)}
disabled={credential ? true : false}
/>
</fieldset>
)}
{vendor === VENDOR_GOOGLE && (
@@ -385,33 +559,108 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
</>
)}
{vendor === VENDOR_NUANCE && (
<fieldset>
<label htmlFor="nuance_client_id">
Client ID<span>*</span>
</label>
<input
id="nuance_client_id"
required
type="text"
name="nuance_client_id"
placeholder="Client ID"
value={clientId}
onChange={(e) => setClientId(e.target.value)}
disabled={credential ? true : false}
/>
<label htmlFor="nuance_secret">
Secret<span>*</span>
</label>
<Passwd
id="nuance_secret"
required
name="nuance_secret"
placeholder="Secret Key"
value={secretKey ? getObscuredSecret(secretKey) : secretKey}
onChange={(e) => setSecretKey(e.target.value)}
disabled={credential ? true : false}
/>
</fieldset>
<>
<fieldset>
<label htmlFor="nuance_client_id">
Client ID
{!onPremNuanceSttCheck && !onPremNuanceTtsCheck && (
<span>*</span>
)}
</label>
<input
id="nuance_client_id"
required={!onPremNuanceSttCheck && !onPremNuanceTtsCheck}
type="text"
name="nuance_client_id"
placeholder="Client ID"
value={clientId}
onChange={(e) => setClientId(e.target.value)}
disabled={credential ? true : false}
/>
<label htmlFor="nuance_secret">
Secret
{!onPremNuanceSttCheck && !onPremNuanceTtsCheck && (
<span>*</span>
)}
</label>
<Passwd
id="nuance_secret"
required={!onPremNuanceSttCheck && !onPremNuanceTtsCheck}
name="nuance_secret"
placeholder="Secret Key"
value={secretKey ? getObscuredSecret(secretKey) : secretKey}
onChange={(e) => setSecretKey(e.target.value)}
disabled={credential ? true : false}
/>
</fieldset>
<fieldset>
<>
<Checkzone
hidden
name="on_prem_nuance_use_tts"
label="Use on-prem TTS"
initialCheck={initialOnPremNuanceTtsCheck}
handleChecked={(e) => {
setOnPremNuanceTtsCheck(e.target.checked);
if (!e.target.checked) {
setTmpOnPremNuanceTtsUrl(onPremNuanceTtsUrl);
setOnPremNuanceTtsUrl("");
} else {
setOnPremNuanceTtsUrl(tmpOnPremNuanceTtsUrl);
}
}}
>
<label htmlFor="on_prem_nuance_use_tts">
TTS URI<span>*</span>
</label>
<input
id="on_prem_nuance_use_tts"
type="text"
name="on_prem_nuance_use_tts"
placeholder="ip:port"
pattern="(.*):([0-9]{0,6}$)"
required={onPremNuanceTtsCheck}
value={onPremNuanceTtsUrl}
onChange={(e) => {
setOnPremNuanceTtsUrl(e.target.value);
}}
/>
</Checkzone>
<Checkzone
hidden
name="on_prem_nuance_use_stt"
label="Use on-prem STT"
initialCheck={initialOnPremNuanceSttCheck}
handleChecked={(e) => {
setOnPremNuanceSttCheck(e.target.checked);
if (!e.target.checked) {
setTmpOnPremNuanceSttUrl(onPremNuanceSttUrl);
setOnPremNuanceSttUrl("");
} else {
setOnPremNuanceSttUrl(tmpOnPremNuanceSttUrl);
}
}}
>
<label htmlFor="on_prem_nuance_use_stt_lb">
STT URI<span>*</span>
</label>
<input
id="on_prem_nuance_use_stt"
type="text"
name="on_prem_nuance_use_stt"
placeholder="ip:port"
pattern="(.*):([0-9]{0,6}$)"
required={onPremNuanceSttCheck}
value={onPremNuanceSttUrl}
onChange={(e) => {
setOnPremNuanceSttUrl(e.target.value);
}}
/>
</Checkzone>
</>
</fieldset>
</>
)}
{vendor === VENDOR_AWS && (
<fieldset>
@@ -448,7 +697,8 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
)}
{(vendor === VENDOR_MICROSOFT ||
vendor === VENDOR_WELLSAID ||
vendor === VENDOR_DEEPGRAM) && (
vendor === VENDOR_DEEPGRAM ||
vendor === VENDOR_SONIOX) && (
<fieldset>
<label htmlFor={`${vendor}_apikey`}>
API key<span>*</span>

View File

@@ -26,6 +26,7 @@ import type { SpeechCredential, Account } from "src/api/types";
import { ScopedAccess } from "src/components/scoped-access";
import { Scope } from "src/store/types";
import { getAccountFilter, setLocation } from "src/store/localStore";
import { VENDOR_CUSTOM } from "src/vendor";
export const SpeechServices = () => {
const user = useSelectState("user");
@@ -140,7 +141,14 @@ export const SpeechServices = () => {
title="Edit application"
className="i"
>
<strong>Vendor: {credential.vendor}</strong>
<strong>
Vendor:{" "}
{credential.vendor.startsWith(VENDOR_CUSTOM)
? credential.vendor.substring(
VENDOR_CUSTOM.length + 1
)
: credential.vendor}
</strong>
<Icons.ArrowRight />
</Link>
</ScopedAccess>

View File

@@ -4,13 +4,13 @@
import React, { useContext } from "react";
import { useNavigate } from "react-router-dom";
import { postLogin } from "src/api";
import { postLogin, postLogout } from "src/api";
import { StatusCodes } from "src/api/types";
import {
ROUTE_LOGIN,
ROUTE_CREATE_PASSWORD,
ROUTE_INTERNAL_ACCOUNTS,
ROUTE_INTERNAL_APPLICATIONS,
ROUTE_LOGIN,
} from "./routes";
import {
SESS_OLD_PASSWORD,
@@ -136,10 +136,27 @@ export const useProvideAuth = (): AuthStateContext => {
};
const signout = () => {
localStorage.clear();
sessionStorage.clear();
sessionStorage.setItem(SESS_FLASH_MSG, MSG_LOGGED_OUT);
window.location.href = ROUTE_LOGIN;
return new Promise((resolve, reject) => {
postLogout()
.then((response) => {
if (response.status === StatusCodes.OK) {
localStorage.clear();
sessionStorage.clear();
sessionStorage.setItem(SESS_FLASH_MSG, MSG_LOGGED_OUT);
resolve(response.json);
}
})
.catch((error) => {
localStorage.clear();
sessionStorage.clear();
sessionStorage.setItem(SESS_FLASH_MSG, MSG_LOGGED_OUT);
if (error) {
reject(error);
}
reject(MSG_SOMETHING_WRONG);
});
});
};
return {

View File

@@ -225,6 +225,17 @@ export const createFilterString = (filterValue: string, label: string) => {
return filterString.join("/");
};
export const disableDefaultTrunkRouting = (userScope: UserData["scope"]) => {
if (import.meta.env.VITE_APP_DISABLE_DEFAULT_TRUNK_ROUTING) {
if (userScope === USER_ADMIN) {
return true;
} else {
return false;
}
}
return true;
};
export {
withSuspense,
useMobileMedia,

13
src/vendor/index.tsx vendored
View File

@@ -17,6 +17,8 @@ export const VENDOR_NUANCE = "nuance";
export const VENDOR_DEEPGRAM = "deepgram";
export const VENDOR_IBM = "ibm";
export const VENDOR_NVIDIA = "nvidia";
export const VENDOR_SONIOX = "soniox";
export const VENDOR_CUSTOM = "custom";
export const vendors: VendorOptions[] = [
{
@@ -51,6 +53,14 @@ export const vendors: VendorOptions[] = [
name: "WellSaid",
value: VENDOR_WELLSAID,
},
{
name: "Soniox",
value: VENDOR_SONIOX,
},
{
name: "Custom",
value: VENDOR_CUSTOM,
},
];
export const useRegionVendors = () => {
@@ -104,6 +114,7 @@ export const useSpeechVendors = () => {
import("./speech-recognizer/deepgram-speech-recognizer-lang"),
import("./speech-recognizer/ibm-speech-recognizer-lang"),
import("./speech-recognizer/nvidia-speech-recognizer-lang"),
import("./speech-recognizer/soniox-speech-recognizer-lang"),
import("./speech-synthesis/aws-speech-synthesis-lang"),
import("./speech-synthesis/google-speech-synthesis-lang"),
import("./speech-synthesis/ms-speech-synthesis-lang"),
@@ -120,6 +131,7 @@ export const useSpeechVendors = () => {
{ default: deepgramRecognizer },
{ default: ibmRecognizer },
{ default: nvidiaRecognizer },
{ default: sonioxRecognizer },
{ default: awsSynthesis },
{ default: googleSynthesis },
{ default: msSynthesis },
@@ -147,6 +159,7 @@ export const useSpeechVendors = () => {
deepgram: deepgramRecognizer,
ibm: ibmRecognizer,
nvidia: nvidiaRecognizer,
soniox: sonioxRecognizer,
},
});
}

View File

@@ -0,0 +1,10 @@
import type { Language } from "../types";
export const languages: Language[] = [
{
name: "English (United States)",
code: "en-US",
},
];
export default languages;

5
src/vendor/types.ts vendored
View File

@@ -6,7 +6,9 @@ export type Vendor =
| "Nuance"
| "Deepgram"
| "IBM"
| "Nvidia";
| "Nvidia"
| "Soniox"
| "Custom";
export interface VendorOptions {
name: Vendor;
@@ -62,6 +64,7 @@ export interface RecognizerVendors {
deepgram: Language[];
ibm: Language[];
nvidia: Language[];
soniox: Language[];
}
export interface SynthesisVendors {