mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2026-01-25 02:08:19 +00:00
Compare commits
59 Commits
fix/filter
...
v0.9.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c390715d8 | ||
|
|
dcdc2c0808 | ||
|
|
a3c48e7efb | ||
|
|
6b9167e6b8 | ||
|
|
e7889e1ad3 | ||
|
|
1111e93918 | ||
|
|
4df5709c10 | ||
|
|
760ddd64bb | ||
|
|
bd8612bb67 | ||
|
|
bc68eb8e71 | ||
|
|
ea2713a021 | ||
|
|
1d1909732f | ||
|
|
d3354bbe9d | ||
|
|
d95b8073d3 | ||
|
|
e8355a1dd3 | ||
|
|
8be61ddfad | ||
|
|
05c1d9efaa | ||
|
|
01a5476dfe | ||
|
|
9d2fee64e6 | ||
|
|
3a87f5f1c2 | ||
|
|
a991b56a4e | ||
|
|
6e14207327 | ||
|
|
8d8d46e76e | ||
|
|
7f72d739cd | ||
|
|
c804d60664 | ||
|
|
df3fc8f2b7 | ||
|
|
65e5b511c3 | ||
|
|
dc519bdef9 | ||
|
|
af1ba3a15c | ||
|
|
67b7792d04 | ||
|
|
c5e7eb0d23 | ||
|
|
6ddcb82adc | ||
|
|
8b9c7ca9c0 | ||
|
|
353c7cfff8 | ||
|
|
7828dc3827 | ||
|
|
213267f682 | ||
|
|
cf056ae6f1 | ||
|
|
1c16d707ca | ||
|
|
2f2e58e180 | ||
|
|
eae674b992 | ||
|
|
aa7889a0d8 | ||
|
|
a892550b06 | ||
|
|
053f8e509f | ||
|
|
fc40695828 | ||
|
|
42af4f6243 | ||
|
|
7ec8065977 | ||
|
|
d8f05da6fd | ||
|
|
15c2b955ca | ||
|
|
87b3ca7e94 | ||
|
|
adafff7ec3 | ||
|
|
bc9a2464fd | ||
|
|
2a6f8c272c | ||
|
|
f031c47228 | ||
|
|
2e9b86c0c4 | ||
|
|
dd93bedd0e | ||
|
|
e2157ce50e | ||
|
|
a382f21f86 | ||
|
|
a20e1513bc | ||
|
|
af8c09587c |
4
.env
4
.env
@@ -1,5 +1,5 @@
|
||||
VITE_API_BASE_URL=http://127.0.0.1:3000/v1
|
||||
VITE_DEV_BASE_URL=http://127.0.0.1:3000/v1
|
||||
#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
|
||||
|
||||
6
.github/workflows/pr-checks.yml
vendored
6
.github/workflows/pr-checks.yml
vendored
@@ -12,18 +12,18 @@ jobs:
|
||||
pr-checks:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Cache node_modules
|
||||
id: node-cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: node-modules-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
- name: Cache cypress binary
|
||||
id: cypress-cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /home/runner/.cache/Cypress
|
||||
key: cypress-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
4
LICENSE
4
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Drachtio Communications Services, LLC
|
||||
Copyright (c) 2018-2024 FirstFive8, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
@@ -44,7 +44,7 @@ declare global {
|
||||
*/
|
||||
mountTestProvider(
|
||||
component: React.ReactNode,
|
||||
options?: MountOptions & { authProps?: TestProviderProps["authProps"] }
|
||||
options?: MountOptions & { authProps?: TestProviderProps["authProps"] },
|
||||
): Cypress.Chainable<MountReturn>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ export const postAccount = (payload: Partial<Account>) => {
|
||||
export const putAccount = (sid: string, payload: Partial<Account>) => {
|
||||
return putFetch<EmptyResponse, Partial<Account>>(
|
||||
`${API_ACCOUNTS}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
10988
package-lock.json
generated
10988
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
62
package.json
62
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jambonz-webapp",
|
||||
"description": "A simple provisioning web app for jambonz",
|
||||
"version": "0.8.4",
|
||||
"version": "0.9.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
@@ -42,45 +42,45 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@jambonz/ui-kit": "^0.0.21",
|
||||
"@stripe/react-stripe-js": "^2.1.1",
|
||||
"@stripe/stripe-js": "^1.54.1",
|
||||
"dayjs": "^1.11.5",
|
||||
"@stripe/react-stripe-js": "^2.6.2",
|
||||
"@stripe/stripe-js": "^3.2.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"react": "^18.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dnd": "16.0.1",
|
||||
"react-dnd-html5-backend": "16.0.1",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-feather": "^2.0.10",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"wavesurfer.js": "^7.3.4"
|
||||
"react-router-dom": "^6.22.3",
|
||||
"wavesurfer.js": "^7.7.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^18.6.1",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.6",
|
||||
"@typescript-eslint/parser": "^5.30.6",
|
||||
"@vitejs/plugin-react": "^1.3.0",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.12.5",
|
||||
"@types/react": "^18.2.74",
|
||||
"@types/react-dom": "^18.2.24",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "^7.5.0",
|
||||
"@typescript-eslint/parser": "^7.5.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"cors": "^2.8.5",
|
||||
"cypress": "^10.8.0",
|
||||
"cypress": "^13.7.2",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"express": "^4.18.1",
|
||||
"husky": "^8.0.1",
|
||||
"lint-staged": "^13.0.3",
|
||||
"nanoid": "^4.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.53.0",
|
||||
"serve": "^14.0.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.6.3",
|
||||
"vite": "^3.0.0"
|
||||
"express": "^4.19.2",
|
||||
"husky": "^9.0.11",
|
||||
"lint-staged": "^15.2.2",
|
||||
"nanoid": "^5.0.7",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.74.1",
|
||||
"serve": "^14.2.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.4",
|
||||
"vite": "^5.2.8"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": "eslint --max-warnings=0",
|
||||
|
||||
@@ -111,14 +111,14 @@ app.get(
|
||||
page: query.page,
|
||||
data: paged,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
app.get(
|
||||
"/api/Accounts/:account_sid/RecentCalls/:call_sid",
|
||||
(req: Request, res: Response) => {
|
||||
res.status(200).json({ total: Math.random() > 0.5 ? 1 : 0 });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
app.get(
|
||||
@@ -126,7 +126,7 @@ app.get(
|
||||
(req: Request, res: Response) => {
|
||||
/** Sample pcap file from: https://wiki.wireshark.org/SampleCaptures#sip-and-rtp */
|
||||
const pcap: Buffer = fs.readFileSync(
|
||||
path.resolve(process.cwd(), "server", "sample-sip-rtp-traffic.pcap")
|
||||
path.resolve(process.cwd(), "server", "sample-sip-rtp-traffic.pcap"),
|
||||
);
|
||||
|
||||
res
|
||||
@@ -136,7 +136,7 @@ app.get(
|
||||
"Content-Disposition": "attachment",
|
||||
})
|
||||
.send(pcap); // server: Buffer => client: Blob
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
app.get(
|
||||
@@ -144,7 +144,7 @@ app.get(
|
||||
(req: Request, res: Response) => {
|
||||
/** Sample pcap file from: https://wiki.wireshark.org/SampleCaptures#sip-and-rtp */
|
||||
const wav: Buffer = fs.readFileSync(
|
||||
path.resolve(process.cwd(), "server", "example.mp3")
|
||||
path.resolve(process.cwd(), "server", "example.mp3"),
|
||||
);
|
||||
|
||||
res
|
||||
@@ -154,7 +154,7 @@ app.get(
|
||||
"Content-Disposition": "attachment",
|
||||
})
|
||||
.send(wav); // server: Buffer => client: Blob
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
app.get(
|
||||
@@ -162,10 +162,10 @@ app.get(
|
||||
(req: Request, res: Response) => {
|
||||
const json = fs.readFileSync(
|
||||
path.resolve(process.cwd(), "server", "sample-jaeger.json"),
|
||||
{ encoding: "utf8" }
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
res.status(200).json(JSON.parse(json));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/** Alerts mock API responses for local dev */
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { hasValue } from "src/utils";
|
||||
import type {
|
||||
Currency,
|
||||
ElevenLabsOptions,
|
||||
LimitField,
|
||||
LimitUnitOption,
|
||||
PasswordSettings,
|
||||
PlayHTOptions,
|
||||
RimelabsOptions,
|
||||
SelectorOptions,
|
||||
SipGateway,
|
||||
SmppGateway,
|
||||
@@ -34,8 +38,11 @@ declare global {
|
||||
}
|
||||
|
||||
/** https://vitejs.dev/guide/env-and-mode.html#env-files */
|
||||
export const API_BASE_URL =
|
||||
const CONFIGURED_API_BASE_URL =
|
||||
window.JAMBONZ?.API_BASE_URL || import.meta.env.VITE_API_BASE_URL;
|
||||
export const API_BASE_URL = hasValue(CONFIGURED_API_BASE_URL)
|
||||
? CONFIGURED_API_BASE_URL
|
||||
: `${window.location.protocol}//${window.location.hostname}/api/v1`;
|
||||
|
||||
/** Serves mock API responses from a local dev API server */
|
||||
export const DEV_BASE_URL = import.meta.env.VITE_DEV_BASE_URL;
|
||||
@@ -200,11 +207,51 @@ export const AUDIO_FORMAT_OPTIONS = [
|
||||
|
||||
export const DEFAULT_ELEVENLABS_MODEL = "eleven_multilingual_v2";
|
||||
|
||||
export const ELEVENLABS_MODEL_OPTIONS = [
|
||||
{ name: "Multilingual v2", value: "eleven_multilingual_v2" },
|
||||
{ name: "Multilingual v1", value: "eleven_multilingual_v1" },
|
||||
{ name: "English v1", value: "eleven_monolingual_v1" },
|
||||
export const DEFAULT_WHISPER_MODEL = "tts-1";
|
||||
|
||||
// VERBIO
|
||||
export const VERBIO_STT_MODELS = [
|
||||
{ name: "V1", value: "V1" },
|
||||
{ name: "V2", value: "V2" },
|
||||
];
|
||||
|
||||
export const DEFAULT_VERBIO_MODEL = "V1";
|
||||
|
||||
// Google Custom Voice reported usage options
|
||||
|
||||
export const DEFAULT_GOOGLE_CUSTOM_VOICES_REPORTED_USAGE = "REALTIME";
|
||||
export const GOOGLE_CUSTOM_VOICES_REPORTED_USAGE = [
|
||||
{ name: "REPORTED_USAGE_UNSPECIFIED", value: "REPORTED_USAGE_UNSPECIFIED" },
|
||||
{ name: "REALTIME", value: "REALTIME" },
|
||||
{ name: "OFFLINE", value: "OFFLINE" },
|
||||
];
|
||||
// ElevenLabs options
|
||||
export const DEFAULT_ELEVENLABS_OPTIONS: Partial<ElevenLabsOptions> = {
|
||||
optimize_streaming_latency: 3,
|
||||
voice_settings: {
|
||||
stability: 0.5,
|
||||
similarity_boost: 0.5,
|
||||
use_speaker_boost: true,
|
||||
},
|
||||
};
|
||||
|
||||
// Rimelabs options
|
||||
export const DEFAULT_RIMELABS_OPTIONS: Partial<RimelabsOptions> = {
|
||||
speedAlpha: 1.0,
|
||||
reduceLatency: true,
|
||||
};
|
||||
|
||||
// PlayHT options
|
||||
export const DEFAULT_PLAYHT_OPTIONS: Partial<PlayHTOptions> = {
|
||||
quality: "medium",
|
||||
speed: 1,
|
||||
seed: 1,
|
||||
temperature: 1,
|
||||
emotion: "female_happy",
|
||||
voice_guidance: 3,
|
||||
style_guidance: 20,
|
||||
text_guidance: 1,
|
||||
};
|
||||
/** Password Length options */
|
||||
|
||||
export const PASSWORD_MIN = 8;
|
||||
@@ -352,3 +399,4 @@ export const API_PRICE = `${API_BASE_URL}/Prices`;
|
||||
export const API_SUBSCRIPTIONS = `${API_BASE_URL}/Subscriptions`;
|
||||
export const API_CHANGE_PASSWORD = `${API_BASE_URL}/change-password`;
|
||||
export const API_SIGNIN = `${API_BASE_URL}/signin`;
|
||||
export const API_GOOGLE_CUSTOM_VOICES = `${API_BASE_URL}/GoogleCustomVoices`;
|
||||
|
||||
227
src/api/index.ts
227
src/api/index.ts
@@ -33,6 +33,7 @@ import {
|
||||
API_SUBSCRIPTIONS,
|
||||
API_CHANGE_PASSWORD,
|
||||
API_SIGNIN,
|
||||
API_GOOGLE_CUSTOM_VOICES,
|
||||
} from "./constants";
|
||||
import { ROUTE_LOGIN } from "src/router/routes";
|
||||
import {
|
||||
@@ -90,10 +91,9 @@ import type {
|
||||
DeleteAccount,
|
||||
ChangePassword,
|
||||
SignIn,
|
||||
GetVoices,
|
||||
LanguageOption,
|
||||
VoiceOption,
|
||||
GetLanguages,
|
||||
GoogleCustomVoice,
|
||||
GoogleCustomVoicesQuery,
|
||||
SpeechSupportedLanguagesAndVoices,
|
||||
} from "./types";
|
||||
import { Availability, StatusCodes } from "./types";
|
||||
import { JaegerRoot } from "./jaeger-types";
|
||||
@@ -101,7 +101,7 @@ import { JaegerRoot } from "./jaeger-types";
|
||||
/** Wrap all requests to normalize response handling */
|
||||
const fetchTransport = <Type>(
|
||||
url: string,
|
||||
options: RequestInit
|
||||
options: RequestInit,
|
||||
): Promise<FetchTransport<Type>> => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
@@ -189,7 +189,7 @@ const getAuthHeaders = () => {
|
||||
|
||||
const getQuery = <Type>(query: Type) => {
|
||||
return decodeURIComponent(
|
||||
new URLSearchParams(query as unknown as Record<string, string>).toString()
|
||||
new URLSearchParams(query as unknown as Record<string, string>).toString(),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -235,7 +235,7 @@ export const getFetch = <Type>(url: string) => {
|
||||
|
||||
export const postFetch = <Type, Payload = undefined>(
|
||||
url: string,
|
||||
payload?: Payload
|
||||
payload?: Payload,
|
||||
) => {
|
||||
return fetchTransport<Type>(url, {
|
||||
method: "POST",
|
||||
@@ -261,7 +261,7 @@ export const deleteFetch = <Type>(url: string) => {
|
||||
|
||||
export const deleteFetchWithPayload = <Type, Payload>(
|
||||
url: string,
|
||||
payload: Payload
|
||||
payload: Payload,
|
||||
) => {
|
||||
return fetchTransport<Type>(url, {
|
||||
method: "DELETE",
|
||||
@@ -291,7 +291,7 @@ export const postLogout = () => {
|
||||
export const postServiceProviders = (payload: Partial<ServiceProvider>) => {
|
||||
return postFetch<SidResponse, Partial<ServiceProvider>>(
|
||||
API_SERVICE_PROVIDERS,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -305,24 +305,24 @@ export const postAccount = (payload: Partial<Account>) => {
|
||||
|
||||
export const postAccountBucketCredentialTest = (
|
||||
sid: string,
|
||||
payload: Partial<BucketCredential>
|
||||
payload: Partial<BucketCredential>,
|
||||
) => {
|
||||
return postFetch<BucketCredentialTestResult, Partial<BucketCredential>>(
|
||||
`${API_ACCOUNTS}/${sid}/BucketCredentialTest`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const postApplication = (payload: Partial<Application>) => {
|
||||
return postFetch<SidResponse, Partial<Application>>(
|
||||
API_APPLICATIONS,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const postSpeechService = (
|
||||
sid: string,
|
||||
payload: Partial<SpeechCredential>
|
||||
payload: Partial<SpeechCredential>,
|
||||
) => {
|
||||
const userData = parseJwt(getToken());
|
||||
const apiUrl =
|
||||
@@ -333,43 +333,17 @@ export const postSpeechService = (
|
||||
return postFetch<SidResponse, Partial<SpeechCredential>>(apiUrl, payload);
|
||||
};
|
||||
|
||||
export const postSpeechServiceVoices = (
|
||||
sid: string,
|
||||
payload: Partial<GetVoices>
|
||||
) => {
|
||||
const userData = parseJwt(getToken());
|
||||
const apiUrl =
|
||||
userData.scope === USER_ACCOUNT
|
||||
? `${API_ACCOUNTS}/${userData.account_sid}/SpeechCredentials/voices`
|
||||
: `${API_SERVICE_PROVIDERS}/${sid}/SpeechCredentials/voices`;
|
||||
|
||||
return postFetch<VoiceOption[], Partial<GetVoices>>(apiUrl, payload);
|
||||
};
|
||||
|
||||
export const postSpeechServiceLanguages = (
|
||||
sid: string,
|
||||
payload: Partial<GetLanguages>
|
||||
) => {
|
||||
const userData = parseJwt(getToken());
|
||||
const apiUrl =
|
||||
userData.scope === USER_ACCOUNT
|
||||
? `${API_ACCOUNTS}/${userData.account_sid}/SpeechCredentials/languages`
|
||||
: `${API_SERVICE_PROVIDERS}/${sid}/SpeechCredentials/languages`;
|
||||
|
||||
return postFetch<LanguageOption[], Partial<GetLanguages>>(apiUrl, payload);
|
||||
};
|
||||
|
||||
export const postMsTeamsTentant = (payload: Partial<MSTeamsTenant>) => {
|
||||
return postFetch<SidResponse, Partial<MSTeamsTenant>>(
|
||||
API_MS_TEAMS_TENANTS,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const postPhoneNumber = (payload: Partial<PhoneNumber>) => {
|
||||
return postFetch<SidResponse, Partial<PhoneNumber>>(
|
||||
API_PHONE_NUMBERS,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -385,19 +359,19 @@ export const postCarrier = (sid: string, payload: Partial<Carrier>) => {
|
||||
|
||||
export const postPredefinedCarrierTemplate = (
|
||||
currentServiceProviderSid: string,
|
||||
predefinedCarrierSid: string
|
||||
predefinedCarrierSid: string,
|
||||
) => {
|
||||
return postFetch<SidResponse>(
|
||||
`${API_BASE_URL}/ServiceProviders/${currentServiceProviderSid}/PredefinedCarriers/${predefinedCarrierSid}`
|
||||
`${API_BASE_URL}/ServiceProviders/${currentServiceProviderSid}/PredefinedCarriers/${predefinedCarrierSid}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const postPredefinedCarrierTemplateAccount = (
|
||||
accountSid: string,
|
||||
predefinedCarrierSid: string
|
||||
predefinedCarrierSid: string,
|
||||
) => {
|
||||
return postFetch<SidResponse>(
|
||||
`${API_BASE_URL}/Accounts/${accountSid}/PredefinedCarriers/${predefinedCarrierSid}`
|
||||
`${API_BASE_URL}/Accounts/${accountSid}/PredefinedCarriers/${predefinedCarrierSid}`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -408,45 +382,45 @@ export const postSipGateway = (payload: Partial<SipGateway>) => {
|
||||
export const postSmppGateway = (payload: Partial<SmppGateway>) => {
|
||||
return postFetch<SidResponse, Partial<SmppGateway>>(
|
||||
API_SMPP_GATEWAY,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const postServiceProviderLimit = (
|
||||
sid: string,
|
||||
payload: Partial<Limit>
|
||||
payload: Partial<Limit>,
|
||||
) => {
|
||||
return postFetch<SidResponse, Partial<Limit>>(
|
||||
`${API_SERVICE_PROVIDERS}/${sid}/Limits`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const postAccountLimit = (sid: string, payload: Partial<Limit>) => {
|
||||
return postFetch<SidResponse, Partial<Limit>>(
|
||||
`${API_ACCOUNTS}/${sid}/Limits`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const postPasswordSettings = (payload: Partial<PasswordSettings>) => {
|
||||
return postFetch<EmptyResponse, Partial<PasswordSettings>>(
|
||||
API_PASSWORD_SETTINGS,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const postForgotPassword = (payload: Partial<ForgotPassword>) => {
|
||||
return postFetch<EmptyResponse, Partial<ForgotPassword>>(
|
||||
API_FORGOT_PASSWORD,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const postSystemInformation = (payload: Partial<SystemInformation>) => {
|
||||
return postFetch<SystemInformation, Partial<SystemInformation>>(
|
||||
API_SYSTEM_INFORMATION,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -454,16 +428,26 @@ export const postLcr = (payload: Partial<Lcr>) => {
|
||||
return postFetch<SidResponse, Partial<Lcr>>(API_LCRS, payload);
|
||||
};
|
||||
|
||||
export const postLcrCreateRoutes = (
|
||||
sid: string,
|
||||
payload: Partial<LcrRoute[]>,
|
||||
) => {
|
||||
return postFetch<EmptyResponse, Partial<LcrRoute[]>>(
|
||||
`${API_LCRS}/${sid}/Routes`,
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const postLcrRoute = (payload: Partial<LcrRoute>) => {
|
||||
return postFetch<SidResponse, Partial<LcrRoute>>(API_LCR_ROUTES, payload);
|
||||
};
|
||||
|
||||
export const postLcrCarrierSetEntry = (
|
||||
payload: Partial<LcrCarrierSetEntry>
|
||||
payload: Partial<LcrCarrierSetEntry>,
|
||||
) => {
|
||||
return postFetch<SidResponse, Partial<LcrCarrierSetEntry>>(
|
||||
API_LCR_CARRIER_SET_ENTRIES,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -474,70 +458,77 @@ export const postClient = (payload: Partial<Client>) => {
|
||||
export const postRegister = (payload: Partial<RegisterRequest>) => {
|
||||
return postFetch<RegisterResponse, Partial<RegisterRequest>>(
|
||||
API_REGISTER,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const postSipRealms = (accountSid: string, domain: string) => {
|
||||
return postFetch<EmptyResponse>(
|
||||
`${API_ACCOUNTS}/${accountSid}/SipRealms/${domain}`
|
||||
`${API_ACCOUNTS}/${accountSid}/SipRealms/${domain}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const postSubscriptions = (payload: Partial<Subscription>) => {
|
||||
return postFetch<Subscription, Partial<Subscription>>(
|
||||
API_SUBSCRIPTIONS,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const postChangepassword = (payload: Partial<ChangePassword>) => {
|
||||
return postFetch<EmptyResponse, Partial<ChangePassword>>(
|
||||
API_CHANGE_PASSWORD,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const postSignIn = (payload: Partial<SignIn>) => {
|
||||
return postFetch<SignIn, Partial<SignIn>>(API_SIGNIN, payload);
|
||||
};
|
||||
|
||||
export const postGoogleCustomVoice = (payload: Partial<GoogleCustomVoice>) => {
|
||||
return postFetch<SidResponse, Partial<GoogleCustomVoice>>(
|
||||
API_GOOGLE_CUSTOM_VOICES,
|
||||
payload,
|
||||
);
|
||||
};
|
||||
/** Named wrappers for `putFetch` */
|
||||
|
||||
export const putUser = (sid: string, payload: Partial<UserUpdatePayload>) => {
|
||||
return putFetch<EmptyResponse, Partial<UserUpdatePayload>>(
|
||||
`${API_USERS}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const putServiceProvider = (
|
||||
sid: string,
|
||||
payload: Partial<ServiceProvider>
|
||||
payload: Partial<ServiceProvider>,
|
||||
) => {
|
||||
return putFetch<EmptyResponse, Partial<ServiceProvider>>(
|
||||
`${API_SERVICE_PROVIDERS}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const putAccount = (sid: string, payload: Partial<Account>) => {
|
||||
return putFetch<EmptyResponse, Partial<Account>>(
|
||||
`${API_ACCOUNTS}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const putApplication = (sid: string, payload: Partial<Application>) => {
|
||||
return putFetch<EmptyResponse, Partial<Application>>(
|
||||
`${API_APPLICATIONS}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const putSpeechService = (
|
||||
sid1: string,
|
||||
sid2: string,
|
||||
payload: Partial<SpeechCredential>
|
||||
payload: Partial<SpeechCredential>,
|
||||
) => {
|
||||
const userData = parseJwt(getToken());
|
||||
const apiUrl =
|
||||
@@ -550,25 +541,25 @@ export const putSpeechService = (
|
||||
|
||||
export const putMsTeamsTenant = (
|
||||
sid: string,
|
||||
payload: Partial<MSTeamsTenant>
|
||||
payload: Partial<MSTeamsTenant>,
|
||||
) => {
|
||||
return putFetch<EmptyResponse, Partial<MSTeamsTenant>>(
|
||||
`${API_MS_TEAMS_TENANTS}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const putPhoneNumber = (sid: string, payload: Partial<PhoneNumber>) => {
|
||||
return putFetch<EmptyResponse, Partial<PhoneNumber>>(
|
||||
`${API_PHONE_NUMBERS}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const putCarrier = (
|
||||
sid1: string,
|
||||
sid2: string,
|
||||
payload: Partial<Carrier>
|
||||
payload: Partial<Carrier>,
|
||||
) => {
|
||||
const userData = parseJwt(getToken());
|
||||
const apiUrl =
|
||||
@@ -582,14 +573,14 @@ export const putCarrier = (
|
||||
export const putSipGateway = (sid: string, payload: Partial<SipGateway>) => {
|
||||
return putFetch<EmptyResponse, Partial<SipGateway>>(
|
||||
`${API_SIP_GATEWAY}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const putSmppGateway = (sid: string, payload: Partial<SmppGateway>) => {
|
||||
return putFetch<EmptyResponse, Partial<SmppGateway>>(
|
||||
`${API_SMPP_GATEWAY}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -597,39 +588,60 @@ export const putLcr = (sid: string, payload: Partial<Lcr>) => {
|
||||
return putFetch<EmptyResponse, Partial<Lcr>>(`${API_LCRS}/${sid}`, payload);
|
||||
};
|
||||
|
||||
export const putLcrUpdateRoutes = (
|
||||
sid: string,
|
||||
payload: Partial<LcrRoute[]>,
|
||||
) => {
|
||||
return putFetch<EmptyResponse, Partial<LcrRoute[]>>(
|
||||
`${API_LCRS}/${sid}/Routes`,
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const putLcrRoutes = (sid: string, payload: Partial<LcrRoute>) => {
|
||||
return putFetch<EmptyResponse, Partial<LcrRoute>>(
|
||||
`${API_LCR_ROUTES}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const putLcrCarrierSetEntries = (
|
||||
sid: string,
|
||||
payload: Partial<LcrCarrierSetEntry>
|
||||
payload: Partial<LcrCarrierSetEntry>,
|
||||
) => {
|
||||
return putFetch<EmptyResponse, Partial<LcrCarrierSetEntry>>(
|
||||
`${API_LCR_CARRIER_SET_ENTRIES}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const putClient = (sid: string, payload: Partial<Client>) => {
|
||||
return putFetch<EmptyResponse, Partial<Client>>(
|
||||
`${API_CLIENTS}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const putActivationCode = (
|
||||
code: string,
|
||||
payload: Partial<ActivationCode>
|
||||
payload: Partial<ActivationCode>,
|
||||
) => {
|
||||
return putFetch<EmptyResponse, Partial<ActivationCode>>(
|
||||
`${API_ACTIVATION_CODE}/${code}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
export const putGoogleCustomVoice = (
|
||||
sid: string,
|
||||
payload: Partial<GoogleCustomVoice>,
|
||||
) => {
|
||||
return putFetch<EmptyResponse, Partial<GoogleCustomVoice>>(
|
||||
`${API_GOOGLE_CUSTOM_VOICES}/${sid}`,
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
/** Named wrappers for `deleteFetch` */
|
||||
|
||||
export const deleteUser = (sid: string) => {
|
||||
@@ -647,7 +659,7 @@ export const deleteApiKey = (sid: string) => {
|
||||
export const deleteAccount = (sid: string, payload: Partial<DeleteAccount>) => {
|
||||
return deleteFetchWithPayload<EmptyResponse, Partial<DeleteAccount>>(
|
||||
`${API_ACCOUNTS}/${sid}`,
|
||||
payload
|
||||
payload,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -657,7 +669,7 @@ export const deleteApplication = (sid: string) => {
|
||||
|
||||
export const deleteSpeechService = (sid1: string, sid2: string) => {
|
||||
return deleteFetch<EmptyResponse>(
|
||||
`${API_SERVICE_PROVIDERS}/${sid1}/SpeechCredentials/${sid2}`
|
||||
`${API_SERVICE_PROVIDERS}/${sid1}/SpeechCredentials/${sid2}`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -683,16 +695,16 @@ export const deleteSmppGateway = (sid: string) => {
|
||||
|
||||
export const deleteServiceProviderLimit = (
|
||||
sid: string,
|
||||
cat: LimitCategories
|
||||
cat: LimitCategories,
|
||||
) => {
|
||||
return deleteFetch<EmptyResponse>(
|
||||
`${API_SERVICE_PROVIDERS}/${sid}/Limits?category=${cat}`
|
||||
`${API_SERVICE_PROVIDERS}/${sid}/Limits?category=${cat}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const deleteAccountLimit = (sid: string, cat: LimitCategories) => {
|
||||
return deleteFetch<EmptyResponse>(
|
||||
`${API_ACCOUNTS}/${sid}/Limits?category=${cat}`
|
||||
`${API_ACCOUNTS}/${sid}/Limits?category=${cat}`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -719,6 +731,10 @@ export const deleteClient = (sid: string) => {
|
||||
export const deleteRecord = (url: string) => {
|
||||
return deleteFetch<EmptyResponse>(url);
|
||||
};
|
||||
|
||||
export const deleteGoogleCustomVoice = (sid: string) => {
|
||||
return deleteFetch<EmptyResponse>(`${API_GOOGLE_CUSTOM_VOICES}/${sid}`);
|
||||
};
|
||||
/** Named wrappers for `getFetch` */
|
||||
|
||||
export const getUser = (sid: string) => {
|
||||
@@ -731,7 +747,7 @@ export const getServiceProviders = () => {
|
||||
|
||||
export const getAccountWebhook = (sid: string) => {
|
||||
return getFetch<SecretResponse>(
|
||||
`${API_ACCOUNTS}/${sid}/WebhookSecret?regenerate=true`
|
||||
`${API_ACCOUNTS}/${sid}/WebhookSecret?regenerate=true`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -753,7 +769,7 @@ export const getLcrRoute = (sid: string) => {
|
||||
|
||||
export const getLcrCarrierSetEtries = (sid: string) => {
|
||||
return getFetch<LcrCarrierSetEntry[]>(
|
||||
`${API_LCR_CARRIER_SET_ENTRIES}?lcr_route_sid=${sid}`
|
||||
`${API_LCR_CARRIER_SET_ENTRIES}?lcr_route_sid=${sid}`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -767,10 +783,17 @@ export const getClient = (sid: string) => {
|
||||
|
||||
export const getAvailability = (domain: string) => {
|
||||
return getFetch<Availability>(
|
||||
`${API_AVAILABILITY}?type=subdomain&value=${domain}`
|
||||
`${API_AVAILABILITY}?type=subdomain&value=${domain}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const getGoogleCustomVoices = (
|
||||
query: Partial<GoogleCustomVoicesQuery>,
|
||||
) => {
|
||||
const qryStr = getQuery<Partial<GoogleCustomVoicesQuery>>(query);
|
||||
return getFetch<GoogleCustomVoice[]>(`${API_GOOGLE_CUSTOM_VOICES}?${qryStr}`);
|
||||
};
|
||||
|
||||
/** Wrappers for APIs that can have a mock dev server response */
|
||||
|
||||
export const getMe = () => {
|
||||
@@ -783,7 +806,7 @@ export const getRecentCalls = (sid: string, query: Partial<CallQuery>) => {
|
||||
return getFetch<PagedResponse<RecentCall>>(
|
||||
import.meta.env.DEV
|
||||
? `${DEV_BASE_URL}/Accounts/${sid}/RecentCalls?${qryStr}`
|
||||
: `${API_ACCOUNTS}/${sid}/RecentCalls?${qryStr}`
|
||||
: `${API_ACCOUNTS}/${sid}/RecentCalls?${qryStr}`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -791,7 +814,7 @@ export const getRecentCall = (sid: string, sipCallId: string) => {
|
||||
return getFetch<TotalResponse>(
|
||||
import.meta.env.DEV
|
||||
? `${DEV_BASE_URL}/Accounts/${sid}/RecentCalls/${sipCallId}`
|
||||
: `${API_ACCOUNTS}/${sid}/RecentCalls/${sipCallId}`
|
||||
: `${API_ACCOUNTS}/${sid}/RecentCalls/${sipCallId}`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -799,7 +822,7 @@ export const getPcap = (sid: string, sipCallId: string, method: string) => {
|
||||
return getBlob(
|
||||
import.meta.env.DEV
|
||||
? `${DEV_BASE_URL}/Accounts/${sid}/RecentCalls/${sipCallId}/${method}/pcap`
|
||||
: `${API_ACCOUNTS}/${sid}/RecentCalls/${sipCallId}/${method}/pcap`
|
||||
: `${API_ACCOUNTS}/${sid}/RecentCalls/${sipCallId}/${method}/pcap`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -807,30 +830,30 @@ export const getJaegerTrace = (sid: string, traceId: string) => {
|
||||
return getFetch<JaegerRoot>(
|
||||
import.meta.env.DEV
|
||||
? `${DEV_BASE_URL}/Accounts/${sid}/RecentCalls/trace/${traceId}`
|
||||
: `${API_ACCOUNTS}/${sid}/RecentCalls/trace/${traceId}`
|
||||
: `${API_ACCOUNTS}/${sid}/RecentCalls/trace/${traceId}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const getServiceProviderRecentCall = (
|
||||
sid: string,
|
||||
sipCallId: string
|
||||
sipCallId: string,
|
||||
) => {
|
||||
return getFetch<TotalResponse>(
|
||||
import.meta.env.DEV
|
||||
? `${DEV_BASE_URL}/ServiceProviders/${sid}/RecentCalls/${sipCallId}`
|
||||
: `${API_SERVICE_PROVIDERS}/${sid}/RecentCalls/${sipCallId}`
|
||||
: `${API_SERVICE_PROVIDERS}/${sid}/RecentCalls/${sipCallId}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const getServiceProviderPcap = (
|
||||
sid: string,
|
||||
sipCallId: string,
|
||||
method: string
|
||||
method: string,
|
||||
) => {
|
||||
return getBlob(
|
||||
import.meta.env.DEV
|
||||
? `${DEV_BASE_URL}/ServiceProviders/${sid}/RecentCalls/${sipCallId}/${method}/pcap`
|
||||
: `${API_SERVICE_PROVIDERS}/${sid}/RecentCalls/${sipCallId}/${method}/pcap`
|
||||
: `${API_SERVICE_PROVIDERS}/${sid}/RecentCalls/${sipCallId}/${method}/pcap`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -840,7 +863,7 @@ export const getAlerts = (sid: string, query: Partial<PageQuery>) => {
|
||||
return getFetch<PagedResponse<Alert>>(
|
||||
import.meta.env.DEV
|
||||
? `${DEV_BASE_URL}/Accounts/${sid}/Alerts?${qryStr}`
|
||||
: `${API_ACCOUNTS}/${sid}/Alerts?${qryStr}`
|
||||
: `${API_ACCOUNTS}/${sid}/Alerts?${qryStr}`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -848,6 +871,22 @@ export const getPrice = () => {
|
||||
return getFetch<PriceInfo[]>(API_PRICE);
|
||||
};
|
||||
|
||||
export const getSpeechSupportedLanguagesAndVoices = (
|
||||
sid: string | undefined,
|
||||
vendor: string,
|
||||
label: string,
|
||||
) => {
|
||||
const userData = parseJwt(getToken());
|
||||
const apiUrl =
|
||||
(userData.scope === USER_ACCOUNT
|
||||
? `${API_ACCOUNTS}/${userData.account_sid}`
|
||||
: `${API_SERVICE_PROVIDERS}/${sid}`) +
|
||||
`/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=${vendor}${
|
||||
label ? `&label=${label}` : ""
|
||||
}`;
|
||||
return getFetch<SpeechSupportedLanguagesAndVoices>(apiUrl);
|
||||
};
|
||||
|
||||
/** Hooks for components to fetch data with refetch method */
|
||||
|
||||
/** :GET /{apiPath} -- this is generic for any fetch of data collections */
|
||||
@@ -907,7 +946,7 @@ export const useServiceProviderData: UseApiData = <Type>(apiPath: string) => {
|
||||
|
||||
if (currentServiceProvider) {
|
||||
getFetch<Type>(
|
||||
`${API_SERVICE_PROVIDERS}/${currentServiceProvider.service_provider_sid}/${apiPath}`
|
||||
`${API_SERVICE_PROVIDERS}/${currentServiceProvider.service_provider_sid}/${apiPath}`,
|
||||
)
|
||||
.then(({ json }) => {
|
||||
if (!ignore) {
|
||||
|
||||
@@ -42,6 +42,18 @@ export interface WaveSurferSttResult {
|
||||
transcript: string;
|
||||
confidence: number;
|
||||
language_code: string;
|
||||
latency?: number;
|
||||
}
|
||||
|
||||
export interface WaveSurferTtsLatencyResult {
|
||||
vendor: string;
|
||||
latency: string;
|
||||
isCached: string;
|
||||
}
|
||||
|
||||
export interface WaveSurferGatherSpeechVerbHookLatencyResult {
|
||||
statusCode: number;
|
||||
latency: string;
|
||||
}
|
||||
|
||||
export interface WaveSurferDtmfResult {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Vendor } from "src/vendor/types";
|
||||
import type { Language, Model, Vendor, VoiceLanguage } from "src/vendor/types";
|
||||
|
||||
/** Simple types */
|
||||
|
||||
@@ -63,11 +63,9 @@ export interface FetchError {
|
||||
}
|
||||
|
||||
export interface UseApiData {
|
||||
<Type>(apiPath: string): [
|
||||
Type | undefined,
|
||||
() => void,
|
||||
FetchError | undefined
|
||||
];
|
||||
<Type>(
|
||||
apiPath: string,
|
||||
): [Type | undefined, () => void, FetchError | undefined];
|
||||
}
|
||||
|
||||
/** API related interfaces */
|
||||
@@ -262,6 +260,7 @@ export interface Account {
|
||||
plan_type?: string;
|
||||
device_to_call_ratio?: number;
|
||||
trial_end_date?: null | string;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
@@ -372,6 +371,14 @@ export interface RecentCall {
|
||||
recording_url?: string;
|
||||
}
|
||||
|
||||
export interface GoogleCustomVoice {
|
||||
google_custom_voice_sid?: string;
|
||||
speech_credential_sid?: string;
|
||||
name: string;
|
||||
reported_usage: string;
|
||||
model: string;
|
||||
}
|
||||
|
||||
export interface SpeechCredential {
|
||||
speech_credential_sid: string;
|
||||
service_provider_sid: null | string;
|
||||
@@ -383,6 +390,8 @@ export interface SpeechCredential {
|
||||
region: null | string;
|
||||
aws_region: null | string;
|
||||
api_key: null | string;
|
||||
role_arn: null | string;
|
||||
user_id: null | string;
|
||||
access_key_id: null | string;
|
||||
secret_access_key: null | string;
|
||||
service_key: null | string;
|
||||
@@ -393,6 +402,7 @@ export interface SpeechCredential {
|
||||
custom_stt_endpoint_url: null | string;
|
||||
custom_stt_endpoint: null | string;
|
||||
client_id: null | string;
|
||||
client_secret: null | string;
|
||||
secret: null | string;
|
||||
nuance_tts_uri: null | string;
|
||||
nuance_stt_uri: null | string;
|
||||
@@ -408,6 +418,12 @@ export interface SpeechCredential {
|
||||
label: null | string;
|
||||
cobalt_server_uri: null | string;
|
||||
model_id: null | string;
|
||||
voice_engine: null | string;
|
||||
engine_version: null | string;
|
||||
model: null | string;
|
||||
options: null | string;
|
||||
deepgram_stt_uri: null | string;
|
||||
deepgram_stt_use_tls: number;
|
||||
}
|
||||
|
||||
export interface Alert {
|
||||
@@ -472,6 +488,8 @@ export interface SipGateway extends Gateway {
|
||||
protocol?: string;
|
||||
port: number | null;
|
||||
pad_crypto?: boolean;
|
||||
send_options_ping?: boolean;
|
||||
use_sips_scheme?: boolean;
|
||||
}
|
||||
|
||||
export interface SmppGateway extends Gateway {
|
||||
@@ -495,7 +513,7 @@ export interface LcrRoute {
|
||||
lcr_route_sid?: null | string;
|
||||
lcr_sid: null | string;
|
||||
regex: null | string;
|
||||
desciption?: null | string;
|
||||
description?: null | string;
|
||||
priority: number;
|
||||
lcr_carrier_set_entries?: LcrCarrierSetEntry[];
|
||||
}
|
||||
@@ -514,6 +532,9 @@ export interface Client {
|
||||
username: null | string;
|
||||
password?: null | string;
|
||||
is_active: boolean;
|
||||
allow_direct_app_calling: boolean;
|
||||
allow_direct_queue_calling: boolean;
|
||||
allow_direct_user_calling: boolean;
|
||||
}
|
||||
|
||||
export interface PageQuery {
|
||||
@@ -528,6 +549,13 @@ export interface CallQuery extends PageQuery {
|
||||
answered?: string;
|
||||
}
|
||||
|
||||
export interface GoogleCustomVoicesQuery {
|
||||
speech_credential_sid?: string;
|
||||
label?: string;
|
||||
account_sid?: string;
|
||||
service_provider_sid: string;
|
||||
}
|
||||
|
||||
export interface PagedResponse<Type> {
|
||||
page_size: number;
|
||||
total: number;
|
||||
@@ -677,19 +705,39 @@ export interface SignIn {
|
||||
account_sid?: null | string;
|
||||
}
|
||||
|
||||
export interface GetVoices {
|
||||
export interface GetLanguagesAndVoices {
|
||||
vendor: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface VoiceOption extends SelectorOptions {
|
||||
[key: string]: unknown;
|
||||
export interface SpeechSupportedLanguagesAndVoices {
|
||||
tts: VoiceLanguage[];
|
||||
stt: Language[];
|
||||
models: Model[];
|
||||
}
|
||||
|
||||
export interface GetLanguages extends GetVoices {
|
||||
[key: string]: unknown;
|
||||
export interface ElevenLabsOptions {
|
||||
optimize_streaming_latency: number;
|
||||
voice_settings: Partial<{
|
||||
similarity_boost: number;
|
||||
stability: number;
|
||||
style: number;
|
||||
use_speaker_boost: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface LanguageOption extends SelectorOptions {
|
||||
[key: string]: unknown;
|
||||
export interface PlayHTOptions {
|
||||
quality: string;
|
||||
speed: number;
|
||||
seed: number;
|
||||
temperature: number;
|
||||
emotion: string;
|
||||
voice_guidance: number;
|
||||
style_guidance: number;
|
||||
text_guidance: number;
|
||||
}
|
||||
|
||||
export interface RimelabsOptions {
|
||||
speedAlpha: number;
|
||||
reduceLatency: boolean;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import applications from "../../cypress/fixtures/applications.json";
|
||||
|
||||
/** Wrapper to perform React state setup */
|
||||
const ApplicationFilterTestWrapper = (
|
||||
props: Partial<ApplicationFilterProps>
|
||||
props: Partial<ApplicationFilterProps>,
|
||||
) => {
|
||||
const [application, setApplication] = useState("");
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("<ApplicationFilter>", () => {
|
||||
/** Default value is properly set to first option */
|
||||
cy.get("select").should(
|
||||
"have.value",
|
||||
applicationsSorted[0].application_sid
|
||||
applicationsSorted[0].application_sid,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ describe("<ApplicationFilter>", () => {
|
||||
cy.get("select").select(applicationsSorted[1].application_sid);
|
||||
cy.get("select").should(
|
||||
"have.value",
|
||||
applicationsSorted[1].application_sid
|
||||
applicationsSorted[1].application_sid,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -75,7 +75,7 @@ describe("<ApplicationFilter>", () => {
|
||||
it("renders default option", () => {
|
||||
/** Test with the `defaultOption` prop */
|
||||
cy.mount(
|
||||
<ApplicationFilterTestWrapper defaultOption="Choose Application" />
|
||||
<ApplicationFilterTestWrapper defaultOption="Choose Application" />,
|
||||
);
|
||||
|
||||
/** No default value is set when this prop is present */
|
||||
|
||||
@@ -20,7 +20,7 @@ export const ClipBoard = ({ text, id = "", name = "" }: ClipBoardProps) => {
|
||||
toastSuccess(
|
||||
<>
|
||||
<strong>{text}</strong> copied to clipboard
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -28,7 +28,7 @@ export const ClipBoard = ({ text, id = "", name = "" }: ClipBoardProps) => {
|
||||
<>
|
||||
Unable to copy <strong>{text}</strong>, please select the text and
|
||||
right click to copy
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ export const AccountSelect = forwardRef<SelectorRef, AccountSelectProps>(
|
||||
defaultOption,
|
||||
...restProps
|
||||
}: AccountSelectProps,
|
||||
ref
|
||||
ref,
|
||||
) => {
|
||||
useEffect(() => {
|
||||
if (hasLength(accounts) && !accountSid && !defaultOption) {
|
||||
@@ -56,14 +56,14 @@ export const AccountSelect = forwardRef<SelectorRef, AccountSelectProps>(
|
||||
name: account.name,
|
||||
value: account.account_sid,
|
||||
}))
|
||||
: []
|
||||
: [],
|
||||
)}
|
||||
onChange={(e) => setAccountSid(e.target.value)}
|
||||
{...restProps}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
AccountSelect.displayName = "AccountSelect";
|
||||
|
||||
@@ -34,7 +34,7 @@ export const ApplicationSelect = forwardRef<
|
||||
defaultOption,
|
||||
...restProps
|
||||
}: ApplicationSelectProps,
|
||||
ref
|
||||
ref,
|
||||
) => {
|
||||
useEffect(() => {
|
||||
if (hasLength(applications) && !applicationSid && !defaultOption) {
|
||||
@@ -62,14 +62,14 @@ export const ApplicationSelect = forwardRef<
|
||||
name: application.name,
|
||||
value: application.application_sid,
|
||||
}))
|
||||
: []
|
||||
: [],
|
||||
)}
|
||||
onChange={(e) => setApplicationSid(e.target.value)}
|
||||
{...restProps}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ApplicationSelect.displayName = "ApplicationSelect";
|
||||
|
||||
@@ -11,6 +11,7 @@ type CheckzoneProps = {
|
||||
hidden?: boolean;
|
||||
children: React.ReactNode;
|
||||
initialCheck: boolean;
|
||||
disabled?: boolean;
|
||||
handleChecked?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
};
|
||||
|
||||
@@ -28,8 +29,9 @@ export const Checkzone = forwardRef<CheckzoneRef, CheckzoneProps>(
|
||||
children,
|
||||
initialCheck,
|
||||
handleChecked,
|
||||
disabled = false,
|
||||
}: CheckzoneProps,
|
||||
ref
|
||||
ref,
|
||||
) => {
|
||||
const [checked, setChecked] = useState(false);
|
||||
const classesTop = classNames({
|
||||
@@ -51,6 +53,7 @@ export const Checkzone = forwardRef<CheckzoneRef, CheckzoneProps>(
|
||||
<label>
|
||||
<div className="label-container">
|
||||
<input
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
type="checkbox"
|
||||
name={name}
|
||||
@@ -71,7 +74,7 @@ export const Checkzone = forwardRef<CheckzoneRef, CheckzoneProps>(
|
||||
{checked && <div className={classesIn}>{children}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Checkzone.displayName = "Checkzone";
|
||||
|
||||
@@ -25,7 +25,7 @@ export const FileUpload = forwardRef<FileRef, FileProps>(
|
||||
disabled,
|
||||
...restProps
|
||||
}: FileProps,
|
||||
ref
|
||||
ref,
|
||||
) => {
|
||||
const [fileName, setFileName] = useState("");
|
||||
const [focus, setFocus] = useState(false);
|
||||
@@ -73,7 +73,7 @@ export const FileUpload = forwardRef<FileRef, FileProps>(
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
FileUpload.displayName = "FileUpload";
|
||||
|
||||
@@ -52,12 +52,12 @@ export const LocalLimits = ({
|
||||
? LIMITS.filter((limit) =>
|
||||
unit === LIMIT_SESS
|
||||
? !limit.category.includes(LIMIT_MIN)
|
||||
: limit.category.includes(LIMIT_MIN)
|
||||
: limit.category.includes(LIMIT_MIN),
|
||||
)
|
||||
: LIMITS.filter(
|
||||
(limit) =>
|
||||
!limit.category.includes("license") &&
|
||||
!limit.category.includes(LIMIT_MIN)
|
||||
!limit.category.includes(LIMIT_MIN),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -130,7 +130,7 @@ export const LocalLimits = ({
|
||||
}
|
||||
onChange={(e) => {
|
||||
const limit = localLimits.find(
|
||||
(l) => l.category === category
|
||||
(l) => l.category === category,
|
||||
);
|
||||
const value = e.target.value ? Number(e.target.value) : "";
|
||||
|
||||
@@ -139,8 +139,8 @@ export const LocalLimits = ({
|
||||
localLimits.map((l) =>
|
||||
l.category === category
|
||||
? { ...l, quantity: value }
|
||||
: l
|
||||
)
|
||||
: l,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
setLocalLimits([
|
||||
|
||||
@@ -24,7 +24,7 @@ export const Passwd = forwardRef<PasswdRef, PasswdProps>(
|
||||
locked = false,
|
||||
...restProps
|
||||
}: PasswdProps,
|
||||
ref
|
||||
ref,
|
||||
) => {
|
||||
const [reveal, setReveal] = useState(false);
|
||||
|
||||
@@ -55,7 +55,7 @@ export const Passwd = forwardRef<PasswdRef, PasswdProps>(
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Passwd.displayName = "Passwd";
|
||||
|
||||
@@ -20,7 +20,7 @@ type SelectorRef = HTMLSelectElement;
|
||||
export const Selector = forwardRef<SelectorRef, SelectorProps>(
|
||||
(
|
||||
{ id, name, value, options, disabled, ...restProps }: SelectorProps,
|
||||
ref
|
||||
ref,
|
||||
) => {
|
||||
const [focus, setFocus] = useState(false);
|
||||
const classes = {
|
||||
@@ -53,7 +53,7 @@ export const Selector = forwardRef<SelectorRef, SelectorProps>(
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Selector.displayName = "Selector";
|
||||
|
||||
@@ -50,7 +50,7 @@ export const Modal = ({
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>,
|
||||
portal
|
||||
portal,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -87,7 +87,7 @@ export const ModalForm = ({
|
||||
</ButtonGroup>
|
||||
</form>
|
||||
</div>,
|
||||
portal
|
||||
portal,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@ export const ModalClose = ({ children, handleClose }: CloseProps) => {
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>,
|
||||
portal
|
||||
portal,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -147,6 +147,6 @@ export const ModalLoader = ({ children }: LoaderProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
portal
|
||||
portal,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@ export const Pagination = ({
|
||||
(num: number) => {
|
||||
setPageNumber(Math.max(1, Math.min(maxPageNumber, num)));
|
||||
},
|
||||
[maxPageNumber, setPageNumber]
|
||||
[maxPageNumber, setPageNumber],
|
||||
);
|
||||
|
||||
const handleNumberMapping = useCallback(
|
||||
@@ -100,7 +100,7 @@ export const Pagination = ({
|
||||
);
|
||||
}
|
||||
},
|
||||
[maxPageNumber, pageNumber]
|
||||
[maxPageNumber, pageNumber],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -29,7 +29,7 @@ describe("<ScopedAccess>", () => {
|
||||
cy.mountTestProvider(
|
||||
<ScopedAccessTestWrapper scope={Scope.admin} user={user}>
|
||||
<H1>ScopedAccess: admin</H1>
|
||||
</ScopedAccessTestWrapper>
|
||||
</ScopedAccessTestWrapper>,
|
||||
);
|
||||
cy.get(".scope-div").should("exist");
|
||||
});
|
||||
@@ -44,7 +44,7 @@ describe("<ScopedAccess>", () => {
|
||||
cy.mountTestProvider(
|
||||
<ScopedAccessTestWrapper scope={Scope.admin} user={user}>
|
||||
<H1>ScopedAccess: service_provider</H1>
|
||||
</ScopedAccessTestWrapper>
|
||||
</ScopedAccessTestWrapper>,
|
||||
);
|
||||
cy.get(".scope-div").should("not.exist");
|
||||
});
|
||||
@@ -59,7 +59,7 @@ describe("<ScopedAccess>", () => {
|
||||
cy.mountTestProvider(
|
||||
<ScopedAccessTestWrapper scope={Scope.admin} user={user}>
|
||||
<H1>ScopedAccess: account</H1>
|
||||
</ScopedAccessTestWrapper>
|
||||
</ScopedAccessTestWrapper>,
|
||||
);
|
||||
cy.get(".scope-div").should("not.exist");
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ export const SearchFilter = ({
|
||||
setAppearance(false);
|
||||
}
|
||||
},
|
||||
[setFilterValue]
|
||||
[setFilterValue],
|
||||
);
|
||||
|
||||
const handleActive = useCallback(() => {
|
||||
|
||||
@@ -42,7 +42,7 @@ export const SelectFilter = ({
|
||||
setFilterValue(e.target.value);
|
||||
const queryFilter = createFilterString(
|
||||
e.target.value,
|
||||
label as string
|
||||
label as string,
|
||||
);
|
||||
setQueryFilter(queryFilter);
|
||||
|
||||
|
||||
@@ -23,6 +23,6 @@ export const Toast = ({ type, message }: ToastProps) => {
|
||||
{message}
|
||||
</div>
|
||||
</div>,
|
||||
portal
|
||||
portal,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -24,4 +24,3 @@ export const MSG_WEBHOOK_FIELDS = (
|
||||
<span>password</span> fields are required.
|
||||
</>
|
||||
);
|
||||
export const NOT_AVAILABLE_PREFIX = "NotAvalable";
|
||||
|
||||
@@ -76,7 +76,7 @@ export const Navi = ({
|
||||
|
||||
const naviByoFiltered = useMemo(() => {
|
||||
return naviByo.filter(
|
||||
(item) => !item.acl || (item.acl && accessControl[item.acl])
|
||||
(item) => !item.acl || (item.acl && accessControl[item.acl]),
|
||||
);
|
||||
}, [accessControl, currentServiceProvider]);
|
||||
|
||||
@@ -100,7 +100,7 @@ export const Navi = ({
|
||||
toastSuccess(
|
||||
<>
|
||||
Added new service provider <strong>{name}</strong>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
dispatch({ type: "serviceProviders" });
|
||||
setSid(json.sid);
|
||||
@@ -123,7 +123,7 @@ export const Navi = ({
|
||||
setSid(getActiveSP());
|
||||
if (sid) {
|
||||
const serviceProvider = serviceProviders.find(
|
||||
(sp) => sp.service_provider_sid === sid
|
||||
(sp) => sp.service_provider_sid === sid,
|
||||
);
|
||||
|
||||
if (serviceProvider) {
|
||||
|
||||
@@ -75,19 +75,19 @@ export const DeleteAccount = ({
|
||||
getFetch<PhoneNumber[]>(API_PHONE_NUMBERS),
|
||||
getFetch<MSTeamsTenant[]>(API_MS_TEAMS_TENANTS),
|
||||
getFetch<ApiKey[]>(
|
||||
`${API_BASE_URL}/Accounts/${account.account_sid}/ApiKeys`
|
||||
`${API_BASE_URL}/Accounts/${account.account_sid}/ApiKeys`,
|
||||
),
|
||||
]).then(([appsRes, phonesRes, teamsRes, apiKeysRes]) => {
|
||||
if (!ignore) {
|
||||
const used = {
|
||||
apps: appsRes.json.filter(
|
||||
(app) => app.account_sid === account.account_sid
|
||||
(app) => app.account_sid === account.account_sid,
|
||||
),
|
||||
phones: phonesRes.json.filter(
|
||||
(phone) => phone.account_sid === account.account_sid
|
||||
(phone) => phone.account_sid === account.account_sid,
|
||||
),
|
||||
teams: teamsRes.json.filter(
|
||||
(team) => team.account_sid === account.account_sid
|
||||
(team) => team.account_sid === account.account_sid,
|
||||
),
|
||||
apiKeys: apiKeysRes.json,
|
||||
};
|
||||
|
||||
@@ -44,8 +44,8 @@ export const EditSipRealm = () => {
|
||||
getAvailability(`${name}.${userData?.account?.root_domain}`)
|
||||
.then(({ json }) =>
|
||||
setIsValidDomain(
|
||||
Boolean(json.available) && hasValue(name) && name.length != 0
|
||||
)
|
||||
Boolean(json.available) && hasValue(name) && name.length != 0,
|
||||
),
|
||||
)
|
||||
.catch((error) => {
|
||||
setErrorMessage(error.msg);
|
||||
|
||||
@@ -19,14 +19,14 @@ export const EditAccount = () => {
|
||||
const params = useParams();
|
||||
const user = useSelectState("user");
|
||||
const [data, refetch, error] = useApiData<Account>(
|
||||
`Accounts/${params.account_sid}`
|
||||
`Accounts/${params.account_sid}`,
|
||||
);
|
||||
const [limitsData, refetchLimits] = useApiData<Limit[]>(
|
||||
`Accounts/${params.account_sid}/Limits`
|
||||
`Accounts/${params.account_sid}/Limits`,
|
||||
);
|
||||
const [apps] = useApiData<Application[]>("Applications");
|
||||
const [ttsCache, ttsCacheFetcher] = useApiData<TtsCache>(
|
||||
`Accounts/${params.account_sid}/TtsCache`
|
||||
`Accounts/${params.account_sid}/TtsCache`,
|
||||
);
|
||||
|
||||
useScopedRedirect(
|
||||
@@ -36,7 +36,7 @@ export const EditAccount = () => {
|
||||
: ROUTE_INTERNAL_APPLICATIONS,
|
||||
user,
|
||||
"You do not have access to this resource",
|
||||
data
|
||||
data,
|
||||
);
|
||||
|
||||
/** Handle error toast at top level... */
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
PlanType,
|
||||
USER_ACCOUNT,
|
||||
WEBHOOK_METHODS,
|
||||
STRIPE_PUBLISHABLE_KEY,
|
||||
} from "src/api/constants";
|
||||
import { MSG_REQUIRED_FIELDS, MSG_WEBHOOK_FIELDS } from "src/constants";
|
||||
|
||||
@@ -85,11 +86,14 @@ export const AccountForm = ({
|
||||
const user = useSelectState("user");
|
||||
const currentServiceProvider = useSelectState("currentServiceProvider");
|
||||
const [accounts] = useApiData<Account[]>("Accounts");
|
||||
const [invoice] = useApiData<Invoice>("Invoices");
|
||||
// Dont get Invoices if the environment is self-hosted
|
||||
const [invoice] = STRIPE_PUBLISHABLE_KEY
|
||||
? useApiData<Invoice>("Invoices")
|
||||
: [undefined];
|
||||
const [userData] = useApiData<CurrentUserData>("Users/me");
|
||||
const [userCarriers] = useApiData<Carrier[]>(`VoipCarriers`);
|
||||
const [userSpeechs] = useApiData<SpeechCredential[]>(
|
||||
`/Accounts/${params.account_sid}/SpeechCredentials`
|
||||
`/Accounts/${params.account_sid}/SpeechCredentials`,
|
||||
);
|
||||
const [name, setName] = useState("");
|
||||
const [realm, setRealm] = useState("");
|
||||
@@ -183,7 +187,7 @@ export const AccountForm = ({
|
||||
|
||||
if (deleteMessage !== "delete my account") {
|
||||
toastError(
|
||||
"You must type the delete message correctly in order to delete your account."
|
||||
"You must type the delete message correctly in order to delete your account.",
|
||||
);
|
||||
if (
|
||||
deleteMessageRef.current &&
|
||||
@@ -281,7 +285,7 @@ export const AccountForm = ({
|
||||
} else {
|
||||
toastError(json.reason);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -306,7 +310,7 @@ export const AccountForm = ({
|
||||
return limit.quantity === ""
|
||||
? deleteAccountLimit(sid, limit.category)
|
||||
: postAccountLimit(sid, limit);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.then(() => {
|
||||
if (limits) {
|
||||
@@ -357,18 +361,18 @@ export const AccountForm = ({
|
||||
filtered.find(
|
||||
(a) =>
|
||||
a.service_provider_sid !== account.data!.service_provider_sid &&
|
||||
a.name === name
|
||||
a.name === name,
|
||||
)
|
||||
) {
|
||||
setMessage(
|
||||
"The name you have entered is already in use on another one of your accounts."
|
||||
"The name you have entered is already in use on another one of your accounts.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (filtered.find((a) => a.sip_realm === realm)) {
|
||||
setMessage(
|
||||
"The SIP Realm you have entered is already in use on another one of your accounts."
|
||||
"The SIP Realm you have entered is already in use on another one of your accounts.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -518,7 +522,7 @@ export const AccountForm = ({
|
||||
}
|
||||
if (account.data.bucket_credential?.secret_access_key) {
|
||||
setBucketSecretAccessKey(
|
||||
account.data.bucket_credential?.secret_access_key
|
||||
account.data.bucket_credential?.secret_access_key,
|
||||
);
|
||||
}
|
||||
if (account.data.bucket_credential?.region) {
|
||||
@@ -526,7 +530,7 @@ export const AccountForm = ({
|
||||
}
|
||||
if (account.data.bucket_credential?.connection_string) {
|
||||
setAzureConnectionString(
|
||||
account.data.bucket_credential.connection_string
|
||||
account.data.bucket_credential.connection_string,
|
||||
);
|
||||
}
|
||||
if (account.data.bucket_credential?.endpoint) {
|
||||
@@ -536,7 +540,7 @@ export const AccountForm = ({
|
||||
setRecordAllCalls(account.data.record_all_calls ? true : false);
|
||||
}
|
||||
setBucketCredentialChecked(
|
||||
hasValue(bucketVendor) && bucketVendor.length !== 0
|
||||
hasValue(bucketVendor) && bucketVendor.length !== 0,
|
||||
);
|
||||
if (account.data.bucket_credential?.tags) {
|
||||
setBucketTags(account.data.bucket_credential?.tags);
|
||||
@@ -548,11 +552,11 @@ export const AccountForm = ({
|
||||
setBucketGoogleServiceKey(tmpBucketGoogleServiceKey);
|
||||
} else if (account.data.bucket_credential?.service_key) {
|
||||
setBucketGoogleServiceKey(
|
||||
JSON.parse(account.data.bucket_credential?.service_key)
|
||||
JSON.parse(account.data.bucket_credential?.service_key),
|
||||
);
|
||||
}
|
||||
setInitialCheckRecordAllCall(
|
||||
hasValue(bucketVendor) && bucketVendor.length !== 0
|
||||
hasValue(bucketVendor) && bucketVendor.length !== 0,
|
||||
);
|
||||
}
|
||||
}, [account]);
|
||||
@@ -572,14 +576,14 @@ export const AccountForm = ({
|
||||
: { quantity: 0 };
|
||||
const callSessionRecord = products
|
||||
? products.find(
|
||||
(item) => item.name === "concurrent call session"
|
||||
(item) => item.name === "concurrent call session",
|
||||
) || { quantity: 0 }
|
||||
: { quantity: 0 };
|
||||
const quantity =
|
||||
(userData.account.device_to_call_ratio || 0) *
|
||||
(callSessionRecord.quantity || 0) +
|
||||
(registeredDeviceRecord.quantity || 0);
|
||||
const { trial_end_date } = userData.account || {};
|
||||
const { trial_end_date, is_active } = userData.account || {};
|
||||
switch (pType) {
|
||||
case PlanType.TRIAL:
|
||||
setSubscriptionDescription(
|
||||
@@ -588,10 +592,10 @@ export const AccountForm = ({
|
||||
} simultaneous calls and ${quantity} registered devices.${
|
||||
trial_end_date
|
||||
? ` Your free trial will end on ${dayjs(
|
||||
trial_end_date
|
||||
trial_end_date,
|
||||
).format("MMM DD, YYYY")}.`
|
||||
: ""
|
||||
}`
|
||||
}`,
|
||||
);
|
||||
break;
|
||||
case PlanType.PAID:
|
||||
@@ -603,15 +607,21 @@ export const AccountForm = ({
|
||||
CurrencySymbol[invoice.currency || "usd"]
|
||||
}${(invoice.total || 0) / 100} on ${dayjs
|
||||
.unix(Number(invoice.next_payment_attempt))
|
||||
.format("MMM DD, YYYY")}.`
|
||||
.format("MMM DD, YYYY")}.`,
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
case PlanType.FREE:
|
||||
setSubscriptionDescription(
|
||||
`You are currently on the Free plan (trial period expired). You are limited to ${callSessionRecord.quantity} simultaneous calls and ${quantity} registered devices`
|
||||
);
|
||||
if (is_active) {
|
||||
setSubscriptionDescription(
|
||||
`You are currently on the Free plan (trial period expired). You are limited to ${callSessionRecord.quantity} simultaneous calls and ${quantity} registered devices`,
|
||||
);
|
||||
} else {
|
||||
setSubscriptionDescription(
|
||||
"Your free trial has expired. Please upgrade your subscription to a paid plan to continue service",
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Make sure Account page is alway scroll to top to see subscription
|
||||
@@ -623,10 +633,10 @@ export const AccountForm = ({
|
||||
const updateBucketTags = (
|
||||
index: number,
|
||||
key: string,
|
||||
value: typeof bucketTags[number][keyof AwsTag]
|
||||
value: (typeof bucketTags)[number][keyof AwsTag],
|
||||
) => {
|
||||
setBucketTags(
|
||||
bucketTags.map((b, i) => (i === index ? { ...b, [key]: value } : b))
|
||||
bucketTags.map((b, i) => (i === index ? { ...b, [key]: value } : b)),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -693,7 +703,9 @@ export const AccountForm = ({
|
||||
{isDeleteAccount && (
|
||||
<Section slim>
|
||||
<form
|
||||
className="form form--internal"
|
||||
className={`form form--internal ${
|
||||
!account?.data && account?.refetch ? "form--blur" : ""
|
||||
}`}
|
||||
onSubmit={handleDeleteAccount}
|
||||
>
|
||||
<fieldset>
|
||||
@@ -898,7 +910,7 @@ export const AccountForm = ({
|
||||
defaultOption="None"
|
||||
application={[application.stateVal, application.stateSet]}
|
||||
applications={apps.filter(
|
||||
(app) => app.account_sid === account.data!.account_sid
|
||||
(app) => app.account_sid === account.data!.account_sid,
|
||||
)}
|
||||
/>
|
||||
</fieldset>
|
||||
@@ -906,7 +918,7 @@ export const AccountForm = ({
|
||||
})}
|
||||
{webhooks.map((webhook) => {
|
||||
const selectOptions = WEBHOOK_METHODS.filter((wm) =>
|
||||
webhook.prefix === "queue_event_hook" ? wm.name !== "GET" : true
|
||||
webhook.prefix === "queue_event_hook" ? wm.name !== "GET" : true,
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -1163,10 +1175,10 @@ export const AccountForm = ({
|
||||
<code>
|
||||
{JSON.stringify(
|
||||
getObscuredGoogleServiceKey(
|
||||
bucketGoogleServiceKey
|
||||
bucketGoogleServiceKey,
|
||||
),
|
||||
null,
|
||||
2
|
||||
2,
|
||||
)}
|
||||
</code>
|
||||
</pre>
|
||||
@@ -1196,10 +1208,10 @@ export const AccountForm = ({
|
||||
bucketVendor === BUCKET_VENDOR_S3_COMPATIBLE
|
||||
? "S3"
|
||||
: bucketVendor === BUCKET_VENDOR_GOOGLE
|
||||
? "Google Cloud Storage"
|
||||
: bucketVendor === BUCKET_VENDOR_AZURE
|
||||
? "Azure Cloud Storage"
|
||||
: ""}{" "}
|
||||
? "Google Cloud Storage"
|
||||
: bucketVendor === BUCKET_VENDOR_AZURE
|
||||
? "Azure Cloud Storage"
|
||||
: ""}{" "}
|
||||
Tags
|
||||
</label>
|
||||
{hasLength(bucketTags) &&
|
||||
@@ -1239,7 +1251,7 @@ export const AccountForm = ({
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setBucketTags(
|
||||
bucketTags.filter((g2, i2) => i2 !== i)
|
||||
bucketTags.filter((g2, i2) => i2 !== i),
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -30,7 +30,7 @@ export const Accounts = () => {
|
||||
Scope.service_provider,
|
||||
`${ROUTE_INTERNAL_ACCOUNTS}/${user?.account_sid}/edit`,
|
||||
user,
|
||||
"You do not have permissions to manage all accounts"
|
||||
"You do not have permissions to manage all accounts",
|
||||
);
|
||||
|
||||
const handleDelete = () => {
|
||||
@@ -40,7 +40,7 @@ export const Accounts = () => {
|
||||
user.account_sid !== account.account_sid
|
||||
) {
|
||||
toastError(
|
||||
"You do not have permissions to make changes to this Account"
|
||||
"You do not have permissions to make changes to this Account",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export const Accounts = () => {
|
||||
toastSuccess(
|
||||
<>
|
||||
Deleted account <strong>{account.name}</strong>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -71,7 +71,7 @@ export const Accounts = () => {
|
||||
</Icon>
|
||||
</Link>
|
||||
</section>
|
||||
<section className="filters filters--spaced">
|
||||
<section className="filters filters--multi">
|
||||
<SearchFilter
|
||||
placeholder="Filter accounts"
|
||||
filter={[filter, setFilter]}
|
||||
|
||||
@@ -35,7 +35,7 @@ export const ManagePaymentForm = () => {
|
||||
if (json.status === "success") {
|
||||
toastSuccess("Payment completed successfully");
|
||||
navigate(
|
||||
`${ROUTE_INTERNAL_ACCOUNTS}/${userData?.account?.account_sid}/edit`
|
||||
`${ROUTE_INTERNAL_ACCOUNTS}/${userData?.account?.account_sid}/edit`,
|
||||
);
|
||||
} else if (json.status === "action required") {
|
||||
if (stripe) {
|
||||
|
||||
@@ -33,7 +33,7 @@ const SubscriptionForm = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const isModifySubscription = location.pathname.includes(
|
||||
"modify-subscription"
|
||||
"modify-subscription",
|
||||
);
|
||||
const [billingCharge, setBillingCharge] = useState<Subscription | null>(null);
|
||||
const [isShowModalLoader, setIsShowModalLoader] = useState(false);
|
||||
@@ -69,7 +69,7 @@ const SubscriptionForm = () => {
|
||||
if (json.status === "success") {
|
||||
toastSuccess("Payment completed successfully");
|
||||
navigate(
|
||||
`${ROUTE_INTERNAL_ACCOUNTS}/${userData?.account?.account_sid}/edit`
|
||||
`${ROUTE_INTERNAL_ACCOUNTS}/${userData?.account?.account_sid}/edit`,
|
||||
);
|
||||
} else if (json.status === "action required") {
|
||||
if (stripe) {
|
||||
@@ -176,7 +176,7 @@ const SubscriptionForm = () => {
|
||||
.then(() => {
|
||||
toastSuccess("Downgrade to free plan completed successfully");
|
||||
navigate(
|
||||
`${ROUTE_INTERNAL_ACCOUNTS}/${userData?.account?.account_sid}/edit`
|
||||
`${ROUTE_INTERNAL_ACCOUNTS}/${userData?.account?.account_sid}/edit`,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -201,16 +201,16 @@ const SubscriptionForm = () => {
|
||||
})
|
||||
.then(() => {
|
||||
toastSuccess(
|
||||
"Your subscription capacity has been successfully modified."
|
||||
"Your subscription capacity has been successfully modified.",
|
||||
);
|
||||
navigate(
|
||||
`${ROUTE_INTERNAL_ACCOUNTS}/${userData?.account?.account_sid}/edit`
|
||||
`${ROUTE_INTERNAL_ACCOUNTS}/${userData?.account?.account_sid}/edit`,
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
toastError(
|
||||
`The additional capacity you that you requested could not be granted due to a failure processing payment.
|
||||
Please configure a valid credit card for your account and the upgrade will be automatically processed`
|
||||
Please configure a valid credit card for your account and the upgrade will be automatically processed`,
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -254,18 +254,18 @@ const SubscriptionForm = () => {
|
||||
},
|
||||
]);
|
||||
const [originalServiceData, setOriginalServiceData] = useState<ServiceData[]>(
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
const initFeesAndCost = (priceData: PriceInfo[]) => {
|
||||
serviceData.forEach((service) => {
|
||||
const record = priceData.find(
|
||||
(item) => item.category === service.category
|
||||
(item) => item.category === service.category,
|
||||
);
|
||||
|
||||
if (record) {
|
||||
const price = record.prices.find(
|
||||
(item) => item.currency === service.currency
|
||||
(item) => item.currency === service.currency,
|
||||
);
|
||||
|
||||
if (price) {
|
||||
@@ -299,7 +299,7 @@ const SubscriptionForm = () => {
|
||||
|
||||
const getServicePrice = (
|
||||
service: ServiceData,
|
||||
capacity: number
|
||||
capacity: number,
|
||||
): [number, string, number] => {
|
||||
let fees = 0;
|
||||
let feesLabel = "";
|
||||
@@ -311,7 +311,7 @@ const SubscriptionForm = () => {
|
||||
} else if (service.billing_scheme === "tiered") {
|
||||
const filteredTiers = service.tiers
|
||||
? service.tiers.filter(
|
||||
(item) => !item.up_to || item.up_to >= capacityNum
|
||||
(item) => !item.up_to || item.up_to >= capacityNum,
|
||||
)
|
||||
: [];
|
||||
if (filteredTiers.length) {
|
||||
@@ -360,7 +360,7 @@ const SubscriptionForm = () => {
|
||||
const updateServiceData = (
|
||||
index: number,
|
||||
key: string,
|
||||
value: typeof serviceData[number][keyof ServiceData]
|
||||
value: (typeof serviceData)[number][keyof ServiceData],
|
||||
) => {
|
||||
setServiceData(
|
||||
serviceData.map((g, i) =>
|
||||
@@ -370,8 +370,8 @@ const SubscriptionForm = () => {
|
||||
[key]: value,
|
||||
...(key === "capacity" && { cost: Number(value) * g.fees }),
|
||||
}
|
||||
: g
|
||||
)
|
||||
: g,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -389,7 +389,7 @@ const SubscriptionForm = () => {
|
||||
if (isModifySubscription && originalServiceData.length > 0) {
|
||||
setIsDisableSubmitButton(
|
||||
serviceData[0].capacity === originalServiceData[0].capacity &&
|
||||
serviceData[1].capacity === originalServiceData[1].capacity
|
||||
serviceData[1].capacity === originalServiceData[1].capacity,
|
||||
);
|
||||
}
|
||||
setTotal(serviceData.reduce((res, service) => res + service.cost || 0, 0));
|
||||
@@ -506,7 +506,7 @@ const SubscriptionForm = () => {
|
||||
updateServiceData(
|
||||
idx,
|
||||
"capacity",
|
||||
e.target.value ? Number(e.target.value) : ""
|
||||
e.target.value ? Number(e.target.value) : "",
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -66,10 +66,10 @@ export const DeleteApplication = ({
|
||||
(account) =>
|
||||
account.device_calling_application_sid ===
|
||||
application.application_sid ||
|
||||
account.siprec_hook_sid === application.application_sid
|
||||
account.siprec_hook_sid === application.application_sid,
|
||||
),
|
||||
teams: msteamRes.json.filter(
|
||||
(team) => team.application_sid === application.application_sid
|
||||
(team) => team.application_sid === application.application_sid,
|
||||
),
|
||||
};
|
||||
const deletable =
|
||||
|
||||
@@ -15,7 +15,7 @@ export const EditApplication = () => {
|
||||
const params = useParams();
|
||||
const user = useSelectState("user");
|
||||
const [data, refetch, error] = useApiData<Application>(
|
||||
`Applications/${params.application_sid}`
|
||||
`Applications/${params.application_sid}`,
|
||||
);
|
||||
|
||||
useScopedRedirect(
|
||||
@@ -23,7 +23,7 @@ export const EditApplication = () => {
|
||||
ROUTE_INTERNAL_APPLICATIONS,
|
||||
user,
|
||||
"You do not have access to this resource",
|
||||
data
|
||||
data,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
LANG_EN_US,
|
||||
VENDOR_GOOGLE,
|
||||
LANG_EN_US_STANDARD_C,
|
||||
useSpeechVendors,
|
||||
VENDOR_CUSTOM,
|
||||
} from "src/vendor";
|
||||
import {
|
||||
@@ -61,8 +60,8 @@ type ApplicationFormProps = {
|
||||
|
||||
export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
const navigate = useNavigate();
|
||||
const { synthesis, recognizers } = useSpeechVendors();
|
||||
const user = useSelectState("user");
|
||||
const currentServiceProvider = useSelectState("currentServiceProvider");
|
||||
const [accounts] = useServiceProviderData<Account[]>("Accounts");
|
||||
const [applications] = useApiData<Application[]>("Applications");
|
||||
const [applicationName, setApplicationName] = useState("");
|
||||
@@ -166,7 +165,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
useRedirect<Account>(
|
||||
accounts,
|
||||
ROUTE_INTERNAL_ACCOUNTS,
|
||||
"You must create an account before you can create an application."
|
||||
"You must create an account before you can create an application.",
|
||||
);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
@@ -174,7 +173,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
|
||||
if (isUserAccountScope(accountSid, user)) {
|
||||
toastError(
|
||||
"You do not have permissions to make changes to these Speech Credentials"
|
||||
"You do not have permissions to make changes to these Speech Credentials",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -188,11 +187,11 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
a.name === applicationName &&
|
||||
(!application ||
|
||||
!application.data ||
|
||||
a.application_sid !== application.data.application_sid)
|
||||
a.application_sid !== application.data.application_sid),
|
||||
)
|
||||
) {
|
||||
setMessage(
|
||||
"The name you have entered is already in use on another one of your applications."
|
||||
"The name you have entered is already in use on another one of your applications.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -243,7 +242,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
application.refetch();
|
||||
toastSuccess("Application updated successfully");
|
||||
navigate(
|
||||
`${ROUTE_INTERNAL_APPLICATIONS}/${application.data?.application_sid}/edit`
|
||||
`${ROUTE_INTERNAL_APPLICATIONS}/${application.data?.application_sid}/edit`,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -272,7 +271,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
tv.vendor.substring(VENDOR_CUSTOM.length + 1) +
|
||||
` (${VENDOR_CUSTOM})`,
|
||||
value: tv.vendor,
|
||||
})
|
||||
}),
|
||||
);
|
||||
setttsVendorOptions(vendors.concat(v));
|
||||
|
||||
@@ -284,7 +283,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
tv.vendor.substring(VENDOR_CUSTOM.length + 1) +
|
||||
` (${VENDOR_CUSTOM})`,
|
||||
value: tv.vendor,
|
||||
})
|
||||
}),
|
||||
);
|
||||
setSttVendorOptions(vendors.concat(v2));
|
||||
|
||||
@@ -297,7 +296,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
(c) =>
|
||||
c.vendor === synthVendor &&
|
||||
(!c.account_sid || c.account_sid === accountSid) &&
|
||||
c.use_for_tts
|
||||
c.use_for_tts,
|
||||
);
|
||||
let c2 = c1
|
||||
.filter((c) => c.label)
|
||||
@@ -305,11 +304,11 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
Object.assign({
|
||||
name: c.label,
|
||||
value: c.label,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
setTtsLabelOptions(
|
||||
c1.length !== c2.length ? [noneLabelObject, ...c2] : c2
|
||||
c1.length !== c2.length ? [noneLabelObject, ...c2] : c2,
|
||||
);
|
||||
|
||||
c1 = fallbackSpeechSynthsisVendor
|
||||
@@ -317,7 +316,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
(c) =>
|
||||
c.vendor === fallbackSpeechSynthsisVendor &&
|
||||
(!c.account_sid || c.account_sid === accountSid) &&
|
||||
c.use_for_tts
|
||||
c.use_for_tts,
|
||||
)
|
||||
: [];
|
||||
|
||||
@@ -327,17 +326,17 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
Object.assign({
|
||||
name: c.label,
|
||||
value: c.label,
|
||||
})
|
||||
}),
|
||||
);
|
||||
setFallbackTtsLabelOptions(
|
||||
c1.length !== c2.length ? [noneLabelObject, ...c2] : c2
|
||||
c1.length !== c2.length ? [noneLabelObject, ...c2] : c2,
|
||||
);
|
||||
|
||||
c1 = credentials.filter(
|
||||
(c) =>
|
||||
c.vendor === recogVendor &&
|
||||
(!c.account_sid || c.account_sid === accountSid) &&
|
||||
c.use_for_stt
|
||||
c.use_for_stt,
|
||||
);
|
||||
c2 = c1
|
||||
.filter((c) => c.label)
|
||||
@@ -345,11 +344,11 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
Object.assign({
|
||||
name: c.label,
|
||||
value: c.label,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
setSttLabelOptions(
|
||||
c1.length !== c2.length ? [noneLabelObject, ...c2] : c2
|
||||
c1.length !== c2.length ? [noneLabelObject, ...c2] : c2,
|
||||
);
|
||||
|
||||
c1 = fallbackSpeechRecognizerVendor
|
||||
@@ -357,7 +356,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
(c) =>
|
||||
c.vendor === fallbackSpeechRecognizerVendor &&
|
||||
(!c.account_sid || c.account_sid === accountSid) &&
|
||||
c.use_for_stt
|
||||
c.use_for_stt,
|
||||
)
|
||||
: [];
|
||||
c2 = c1
|
||||
@@ -366,11 +365,11 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
Object.assign({
|
||||
name: c.label,
|
||||
value: c.label,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
setFallbackSttLabelOptions(
|
||||
c1.length !== c2.length ? [noneLabelObject, ...c2] : c2
|
||||
c1.length !== c2.length ? [noneLabelObject, ...c2] : c2,
|
||||
);
|
||||
}
|
||||
}, [
|
||||
@@ -387,6 +386,53 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
}
|
||||
}, [accountSid]);
|
||||
|
||||
useEffect(() => {
|
||||
let label: string;
|
||||
// Synthesis Label
|
||||
label = application?.data?.speech_synthesis_label || "";
|
||||
if (ttsLabelOptions && !ttsLabelOptions.find((l) => l.value === label)) {
|
||||
label = ttsLabelOptions.length ? ttsLabelOptions[0].value : "";
|
||||
}
|
||||
setSynthLabel(label);
|
||||
|
||||
// fallback Synthesis Label
|
||||
label = application?.data?.fallback_speech_synthesis_label || "";
|
||||
if (
|
||||
fallbackTtsLabelOptions &&
|
||||
!fallbackTtsLabelOptions.find((l) => l.value === label)
|
||||
) {
|
||||
label = fallbackTtsLabelOptions.length
|
||||
? fallbackTtsLabelOptions[0].value
|
||||
: "";
|
||||
}
|
||||
setFallbackSpeechSynthsisLabel(label);
|
||||
|
||||
// regconizer label
|
||||
label = application?.data?.speech_recognizer_label || "";
|
||||
if (sttLabelOptions && !sttLabelOptions.find((l) => l.value === label)) {
|
||||
label = sttLabelOptions.length ? sttLabelOptions[0].value : "";
|
||||
}
|
||||
setRecogLabel(label);
|
||||
|
||||
// fallback regconizer label
|
||||
label = application?.data?.fallback_speech_recognizer_label || "";
|
||||
if (
|
||||
fallbackSttLabelOptions &&
|
||||
!fallbackSttLabelOptions.find((l) => l.value === label)
|
||||
) {
|
||||
label = fallbackSttLabelOptions.length
|
||||
? fallbackSttLabelOptions[0].value
|
||||
: "";
|
||||
}
|
||||
setFallbackSpeechRecognizerLabel(label);
|
||||
}, [
|
||||
ttsLabelOptions,
|
||||
sttLabelOptions,
|
||||
fallbackTtsLabelOptions,
|
||||
fallbackSttLabelOptions,
|
||||
application,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setLocation();
|
||||
if (application && application.data) {
|
||||
@@ -398,7 +444,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
setTmpApplicationJson(applicationJson);
|
||||
setInitialApplicationJson(
|
||||
application.data.app_json != undefined &&
|
||||
application.data.app_json.length !== 0
|
||||
application.data.app_json.length !== 0,
|
||||
);
|
||||
|
||||
if (application.data.call_hook) {
|
||||
@@ -445,12 +491,12 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
|
||||
if (application.data.speech_synthesis_vendor)
|
||||
setSynthVendor(
|
||||
application.data.speech_synthesis_vendor as keyof SynthesisVendors
|
||||
application.data.speech_synthesis_vendor as keyof SynthesisVendors,
|
||||
);
|
||||
|
||||
if (application.data.speech_synthesis_language)
|
||||
setSynthLang(
|
||||
application.data.speech_synthesis_language as keyof RecognizerVendors
|
||||
application.data.speech_synthesis_language as keyof RecognizerVendors,
|
||||
);
|
||||
|
||||
if (application.data.speech_synthesis_voice)
|
||||
@@ -458,58 +504,44 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
|
||||
if (application.data.speech_recognizer_vendor)
|
||||
setRecogVendor(
|
||||
application.data.speech_recognizer_vendor as keyof RecognizerVendors
|
||||
application.data.speech_recognizer_vendor as keyof RecognizerVendors,
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (application.data.use_for_fallback_speech) {
|
||||
setUseForFallbackSpeech(application.data.use_for_fallback_speech > 0);
|
||||
setInitalCheckFallbackSpeech(
|
||||
application.data.use_for_fallback_speech > 0
|
||||
application.data.use_for_fallback_speech > 0,
|
||||
);
|
||||
}
|
||||
if (application.data.fallback_speech_recognizer_vendor) {
|
||||
setFallbackSpeechRecognizerVendor(
|
||||
application.data
|
||||
.fallback_speech_recognizer_vendor as keyof RecognizerVendors
|
||||
.fallback_speech_recognizer_vendor as keyof RecognizerVendors,
|
||||
);
|
||||
}
|
||||
if (application.data.fallback_speech_recognizer_language) {
|
||||
setFallbackSpeechRecognizerLanguage(
|
||||
application.data.fallback_speech_recognizer_language
|
||||
);
|
||||
}
|
||||
if (application.data.fallback_speech_recognizer_label) {
|
||||
setFallbackSpeechRecognizerLabel(
|
||||
application.data.fallback_speech_recognizer_label
|
||||
application.data.fallback_speech_recognizer_language,
|
||||
);
|
||||
}
|
||||
|
||||
if (application.data.fallback_speech_synthesis_vendor) {
|
||||
setFallbackSpeechSynthsisVendor(
|
||||
application.data
|
||||
.fallback_speech_synthesis_vendor as keyof SynthesisVendors
|
||||
.fallback_speech_synthesis_vendor as keyof SynthesisVendors,
|
||||
);
|
||||
}
|
||||
if (application.data.fallback_speech_synthesis_language) {
|
||||
setFallbackSpeechSynthsisLanguage(
|
||||
application.data.fallback_speech_synthesis_language
|
||||
application.data.fallback_speech_synthesis_language,
|
||||
);
|
||||
}
|
||||
if (application.data.fallback_speech_synthesis_voice) {
|
||||
setFallbackSpeechSynthsisVoice(
|
||||
application.data.fallback_speech_synthesis_voice
|
||||
);
|
||||
}
|
||||
if (application.data.fallback_speech_synthesis_label) {
|
||||
setFallbackSpeechSynthsisLabel(
|
||||
application.data.fallback_speech_synthesis_label
|
||||
application.data.fallback_speech_synthesis_voice,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -549,7 +581,12 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
|
||||
return (
|
||||
<Section slim>
|
||||
<form className="form form--internal" onSubmit={handleSubmit}>
|
||||
<form
|
||||
className={`form form--internal ${
|
||||
!application?.data && application?.refetch ? "form--blur" : ""
|
||||
}`}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<fieldset>
|
||||
<MS>{MSG_REQUIRED_FIELDS}</MS>
|
||||
</fieldset>
|
||||
@@ -674,15 +711,17 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
);
|
||||
})}
|
||||
<SpeechProviderSelection
|
||||
serviceProviderSid={
|
||||
currentServiceProvider?.service_provider_sid || ""
|
||||
}
|
||||
accountSid={accountSid}
|
||||
credentials={credentials}
|
||||
synthesis={synthesis}
|
||||
ttsVendor={[synthVendor, setSynthVendor]}
|
||||
ttsVendorOptions={ttsVendorOptions}
|
||||
ttsVoice={[synthVoice, setSynthVoice]}
|
||||
ttsLang={[synthLang, setSynthLang]}
|
||||
ttsLabelOptions={ttsLabelOptions}
|
||||
ttsLabel={[synthLabel, setSynthLabel]}
|
||||
recognizers={recognizers}
|
||||
sttVendor={[recogVendor, setRecogVendor]}
|
||||
sttVendorOptions={sttVendorOptions}
|
||||
sttLang={[recogLang, setRecogLang]}
|
||||
@@ -701,8 +740,11 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
}}
|
||||
>
|
||||
<SpeechProviderSelection
|
||||
serviceProviderSid={
|
||||
currentServiceProvider?.service_provider_sid || ""
|
||||
}
|
||||
accountSid={accountSid}
|
||||
credentials={credentials}
|
||||
synthesis={synthesis}
|
||||
ttsVendor={[
|
||||
fallbackSpeechSynthsisVendor,
|
||||
setFallbackSpeechSynthsisVendor,
|
||||
@@ -721,7 +763,6 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
fallbackSpeechSynthsisLabel,
|
||||
setFallbackSpeechSynthsisLabel,
|
||||
]}
|
||||
recognizers={recognizers}
|
||||
sttVendor={[
|
||||
fallbackSpeechRecognizerVendor,
|
||||
setFallbackSpeechRecognizerVendor,
|
||||
|
||||
@@ -40,14 +40,14 @@ export const Applications = () => {
|
||||
|
||||
const filteredApplications = useFilteredResults<Application>(
|
||||
filter,
|
||||
applications
|
||||
applications,
|
||||
);
|
||||
|
||||
const handleDelete = () => {
|
||||
if (application) {
|
||||
if (isUserAccountScope(accountSid, user)) {
|
||||
toastError(
|
||||
"You do not have permissions to make changes to this Application"
|
||||
"You do not have permissions to make changes to this Application",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -59,7 +59,7 @@ export const Applications = () => {
|
||||
toastSuccess(
|
||||
<>
|
||||
Deleted application <strong>{application.name}</strong>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -96,7 +96,7 @@ export const Applications = () => {
|
||||
</Link>
|
||||
)}
|
||||
</section>
|
||||
<section className="filters filters--spaced">
|
||||
<section className="filters filters--multi">
|
||||
<SearchFilter
|
||||
placeholder="Filter applications"
|
||||
filter={[filter, setFilter]}
|
||||
@@ -139,7 +139,7 @@ export const Applications = () => {
|
||||
{
|
||||
accounts?.find(
|
||||
(acct) =>
|
||||
acct.account_sid === application.account_sid
|
||||
acct.account_sid === application.account_sid,
|
||||
)?.name
|
||||
}
|
||||
</span>
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { postSpeechServiceLanguages, postSpeechServiceVoices } from "src/api";
|
||||
import { SpeechCredential } from "src/api/types";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
getGoogleCustomVoices,
|
||||
getSpeechSupportedLanguagesAndVoices,
|
||||
} from "src/api";
|
||||
import { USER_ADMIN } from "src/api/constants";
|
||||
import {
|
||||
SpeechCredential,
|
||||
SpeechSupportedLanguagesAndVoices,
|
||||
} from "src/api/types";
|
||||
import { Selector } from "src/components/forms";
|
||||
import { SelectorOption } from "src/components/forms/selector";
|
||||
import { useSelectState } from "src/store";
|
||||
import { toastError, useSelectState } from "src/store";
|
||||
import { hasLength } from "src/utils";
|
||||
import {
|
||||
ELEVENLABS_LANG_EN,
|
||||
@@ -14,37 +21,36 @@ import {
|
||||
VENDOR_COBALT,
|
||||
VENDOR_CUSTOM,
|
||||
VENDOR_DEEPGRAM,
|
||||
VENDOR_ASSEMBLYAI,
|
||||
VENDOR_ELEVENLABS,
|
||||
VENDOR_GOOGLE,
|
||||
VENDOR_MICROSOFT,
|
||||
VENDOR_SONIOX,
|
||||
VENDOR_WELLSAID,
|
||||
VENDOR_WHISPER,
|
||||
} from "src/vendor";
|
||||
import {
|
||||
LabelOptions,
|
||||
Language,
|
||||
RecognizerVendors,
|
||||
SynthesisVendors,
|
||||
VendorOptions,
|
||||
Voice,
|
||||
VoiceLanguage,
|
||||
} from "src/vendor/types";
|
||||
type SpeechProviderSelectionProbs = {
|
||||
accountSid: string;
|
||||
serviceProviderSid: string;
|
||||
credentials: SpeechCredential[] | undefined;
|
||||
synthesis: SynthesisVendors | undefined;
|
||||
ttsVendor: [
|
||||
keyof SynthesisVendors,
|
||||
React.Dispatch<React.SetStateAction<keyof SynthesisVendors>>
|
||||
React.Dispatch<React.SetStateAction<keyof SynthesisVendors>>,
|
||||
];
|
||||
ttsVendorOptions: VendorOptions[];
|
||||
ttsVoice: [string, React.Dispatch<React.SetStateAction<string>>];
|
||||
ttsLang: [string, React.Dispatch<React.SetStateAction<string>>];
|
||||
ttsLabelOptions: LabelOptions[];
|
||||
ttsLabel: [string, React.Dispatch<React.SetStateAction<string>>];
|
||||
recognizers: RecognizerVendors | undefined;
|
||||
sttVendor: [
|
||||
keyof RecognizerVendors,
|
||||
React.Dispatch<React.SetStateAction<keyof RecognizerVendors>>
|
||||
React.Dispatch<React.SetStateAction<keyof RecognizerVendors>>,
|
||||
];
|
||||
sttVendorOptions: VendorOptions[];
|
||||
sttLang: [string, React.Dispatch<React.SetStateAction<string>>];
|
||||
@@ -53,21 +59,26 @@ type SpeechProviderSelectionProbs = {
|
||||
};
|
||||
|
||||
export const SpeechProviderSelection = ({
|
||||
accountSid,
|
||||
serviceProviderSid,
|
||||
credentials,
|
||||
synthesis,
|
||||
ttsVendor: [synthVendor, setSynthVendor],
|
||||
ttsVendorOptions,
|
||||
ttsVoice: [synthVoice, setSynthVoice],
|
||||
ttsLang: [synthLang, setSynthLang],
|
||||
ttsLabelOptions,
|
||||
ttsLabel: [synthLabel, setSynthLabel],
|
||||
recognizers,
|
||||
sttVendor: [recogVendor, setRecogVendor],
|
||||
sttVendorOptions,
|
||||
sttLang: [recogLang, setRecogLang],
|
||||
sttLabelOptions,
|
||||
sttLabel: [recogLabel, setRecogLabel],
|
||||
}: SpeechProviderSelectionProbs) => {
|
||||
const user = useSelectState("user");
|
||||
const [
|
||||
synthesisSupportedLanguagesAndVoices,
|
||||
setSynthesisSupportedLanguagesAndVoices,
|
||||
] = useState<SpeechSupportedLanguagesAndVoices | null>();
|
||||
const [selectedCredential, setSelectedCredential] = useState<
|
||||
SpeechCredential | undefined
|
||||
>();
|
||||
@@ -77,333 +88,488 @@ export const SpeechProviderSelection = ({
|
||||
const [synthesisLanguageOptions, setSynthesisLanguageOptions] = useState<
|
||||
SelectorOption[]
|
||||
>([]);
|
||||
const [synthesisModelOptions, setSynthesisModelOptions] = useState<
|
||||
SelectorOption[]
|
||||
>([]);
|
||||
const [
|
||||
synthesisGoogleCustomVoiceOptions,
|
||||
setSynthesisGoogleCustomVoiceOptions,
|
||||
] = useState<SelectorOption[]>([]);
|
||||
const [recogLanguageOptions, setRecogLanguageOptions] = useState<
|
||||
SelectorOption[]
|
||||
>([]);
|
||||
|
||||
const currentServiceProvider = useSelectState("currentServiceProvider");
|
||||
const currentTtsVendor = useRef(synthVendor);
|
||||
const currentSttVendor = useRef(recogVendor);
|
||||
const shouldUpdateTtsVoice = useRef(false);
|
||||
const shouldUpdateSttLanguage = useRef(false);
|
||||
const ttsEffectTimer = useRef<number | null>(null);
|
||||
const sttEffectTimer = useRef<number | null>(null);
|
||||
|
||||
// Get Synthesis languages and voices
|
||||
useEffect(() => {
|
||||
if (!synthesis) {
|
||||
if (
|
||||
!user ||
|
||||
!synthVendor ||
|
||||
(user?.scope === USER_ADMIN && !serviceProviderSid)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let options = synthesis[synthVendor as keyof SynthesisVendors]
|
||||
.filter((lang: VoiceLanguage) => {
|
||||
// ELEVENLABS has same voice for all lange, take voices from the 1st language
|
||||
if (synthVendor === VENDOR_ELEVENLABS) {
|
||||
return true;
|
||||
}
|
||||
return lang.code === synthLang;
|
||||
})
|
||||
.flatMap((lang: VoiceLanguage) =>
|
||||
lang.voices.map((voice: Voice) => ({
|
||||
name: voice.name,
|
||||
value: voice.value,
|
||||
}))
|
||||
) as Voice[];
|
||||
setSynthesisVoiceOptions(options);
|
||||
|
||||
options = synthesis[synthVendor as keyof SynthesisVendors].map(
|
||||
(lang: VoiceLanguage) => ({
|
||||
name: lang.name,
|
||||
value: lang.code,
|
||||
})
|
||||
);
|
||||
setSynthesisLanguageOptions(options);
|
||||
|
||||
if (synthVendor === VENDOR_ELEVENLABS) {
|
||||
postSpeechServiceVoices(
|
||||
currentServiceProvider
|
||||
? currentServiceProvider.service_provider_sid
|
||||
: "",
|
||||
{
|
||||
vendor: synthVendor,
|
||||
label: synthLabel,
|
||||
}
|
||||
).then(({ json }) => {
|
||||
if (json.length > 0) {
|
||||
setSynthesisVoiceOptions(json);
|
||||
}
|
||||
});
|
||||
|
||||
postSpeechServiceLanguages(
|
||||
currentServiceProvider
|
||||
? currentServiceProvider.service_provider_sid
|
||||
: "",
|
||||
{
|
||||
vendor: synthVendor,
|
||||
label: synthLabel,
|
||||
}
|
||||
).then(({ json }) => {
|
||||
if (json.length > 0) {
|
||||
setSynthesisLanguageOptions(json);
|
||||
}
|
||||
});
|
||||
currentTtsVendor.current = synthVendor;
|
||||
/** When Custom Vendor is used, user you have to input the lange and voice. */
|
||||
if (synthVendor.toString().startsWith(VENDOR_CUSTOM)) {
|
||||
setSynthVoice("");
|
||||
return;
|
||||
}
|
||||
}, [synthVendor, synthesis, synthLabel]);
|
||||
// just execute last change
|
||||
if (ttsEffectTimer.current) {
|
||||
clearTimeout(ttsEffectTimer.current);
|
||||
}
|
||||
|
||||
ttsEffectTimer.current = setTimeout(() => {
|
||||
configSynthesis();
|
||||
}, 200);
|
||||
}, [synthVendor, synthLabel, serviceProviderSid]);
|
||||
|
||||
// Get Recognizer languages and voices
|
||||
useEffect(() => {
|
||||
/** When Custom Vendor is used, user you have to input the lange and voice. */
|
||||
if (recogVendor.toString().startsWith(VENDOR_CUSTOM)) {
|
||||
setRecogLang(LANG_EN_US);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!user ||
|
||||
!recogVendor ||
|
||||
(user?.scope === USER_ADMIN && !serviceProviderSid)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
currentSttVendor.current = recogVendor;
|
||||
// just execute last change
|
||||
if (sttEffectTimer.current) {
|
||||
clearTimeout(sttEffectTimer.current);
|
||||
}
|
||||
|
||||
sttEffectTimer.current = setTimeout(() => {
|
||||
configRecognizer();
|
||||
}, 200);
|
||||
}, [recogVendor, recogLabel, serviceProviderSid]);
|
||||
|
||||
useEffect(() => {
|
||||
if (credentials) {
|
||||
setSelectedCredential(
|
||||
credentials.find(
|
||||
(c) => c.vendor === synthVendor && c.label === synthLabel
|
||||
)
|
||||
(c) => c.vendor === synthVendor && (c.label || "") === synthLabel,
|
||||
),
|
||||
);
|
||||
}
|
||||
}, [synthVendor, synthLabel, credentials]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!synthLabel && ttsLabelOptions?.length > 0) {
|
||||
setSynthLabel(ttsLabelOptions[0].value);
|
||||
}
|
||||
if (!recogLabel && sttLabelOptions?.length > 0) {
|
||||
setRecogLabel(sttLabelOptions[0].value);
|
||||
}
|
||||
}, [ttsLabelOptions, sttLabelOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (synthesisSupportedLanguagesAndVoices) {
|
||||
// Extract Voice
|
||||
const voicesOpts =
|
||||
synthesisSupportedLanguagesAndVoices.tts?.find((lang) => {
|
||||
if (synthVendor === VENDOR_ELEVENLABS && lang.voices.length > 0) {
|
||||
return true;
|
||||
}
|
||||
return lang.value === synthLang;
|
||||
})?.voices || [];
|
||||
|
||||
if (synthVendor === VENDOR_GOOGLE && synthesisGoogleCustomVoiceOptions) {
|
||||
if (synthesisGoogleCustomVoiceOptions) {
|
||||
setSynthesisVoiceOptions([
|
||||
...synthesisGoogleCustomVoiceOptions,
|
||||
...voicesOpts,
|
||||
]);
|
||||
} else {
|
||||
setSynthesisVoiceOptions(voicesOpts);
|
||||
}
|
||||
if (synthesisGoogleCustomVoiceOptions.length > 0) {
|
||||
updateTtsVoice(synthesisGoogleCustomVoiceOptions[0].value);
|
||||
}
|
||||
} else {
|
||||
setSynthesisVoiceOptions(voicesOpts);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
synthLang,
|
||||
synthesisSupportedLanguagesAndVoices,
|
||||
synthesisGoogleCustomVoiceOptions,
|
||||
]);
|
||||
|
||||
const configSynthesis = () => {
|
||||
getSpeechSupportedLanguagesAndVoices(
|
||||
serviceProviderSid,
|
||||
synthVendor,
|
||||
synthLabel,
|
||||
)
|
||||
.then(({ json }) => {
|
||||
// while fetching data, user might change the vendor
|
||||
if (currentTtsVendor.current !== synthVendor) {
|
||||
return;
|
||||
}
|
||||
setSynthesisSupportedLanguagesAndVoices(json);
|
||||
// Extract model
|
||||
if (json.models && json.models.length) {
|
||||
setSynthesisModelOptions(json.models);
|
||||
if (synthVendor === VENDOR_DEEPGRAM) {
|
||||
setSynthVoice(json.models[0].value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (json.tts && json.tts.length) {
|
||||
// Extract Language
|
||||
const langOpts = json.tts.map((lang) => ({
|
||||
name: lang.name,
|
||||
value: lang.value,
|
||||
}));
|
||||
setSynthesisLanguageOptions(langOpts);
|
||||
|
||||
// Default setting
|
||||
const googleLang = json.tts.find((lang) => lang.value === synthLang);
|
||||
if (
|
||||
synthVendor === VENDOR_GOOGLE &&
|
||||
(!googleLang ||
|
||||
!googleLang.voices.find((v) => v.value === synthVoice))
|
||||
) {
|
||||
setSynthLang(LANG_EN_US);
|
||||
updateTtsVoice(LANG_EN_US_STANDARD_C);
|
||||
return;
|
||||
}
|
||||
if (synthVendor === VENDOR_ELEVENLABS) {
|
||||
// Samve Voices applied to all languages
|
||||
// Voices are only available for the 1st language.
|
||||
setSynthLang(ELEVENLABS_LANG_EN);
|
||||
updateTtsVoice(json.tts[0].voices[0].value);
|
||||
return;
|
||||
}
|
||||
if (synthVendor === VENDOR_WHISPER) {
|
||||
const newLang = json.tts.find((lang) => lang.value === LANG_EN_US);
|
||||
setSynthLang(LANG_EN_US);
|
||||
updateTtsVoice(newLang!.voices[0].value);
|
||||
return;
|
||||
}
|
||||
/** Google and AWS have different language lists */
|
||||
/** If the new language doesn't map then default to "en-US" */
|
||||
let newLang = json.tts.find((lang) => lang.value === synthLang);
|
||||
|
||||
if (newLang) {
|
||||
updateTtsVoice(newLang.voices[0].value);
|
||||
return;
|
||||
}
|
||||
|
||||
newLang = json.tts.find((lang) => lang.value === LANG_EN_US);
|
||||
|
||||
setSynthLang(LANG_EN_US);
|
||||
updateTtsVoice(newLang!.voices[0].value);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toastError(error.msg);
|
||||
});
|
||||
|
||||
if (synthVendor === VENDOR_GOOGLE) {
|
||||
getGoogleCustomVoices({
|
||||
...(synthLabel && { label: synthLabel }),
|
||||
account_sid: accountSid,
|
||||
service_provider_sid: serviceProviderSid,
|
||||
}).then(({ json }) => {
|
||||
// If after successfully fetching data, vendor is still good, then apply value
|
||||
if (currentTtsVendor.current !== VENDOR_GOOGLE) {
|
||||
return;
|
||||
}
|
||||
const customVOices = json.map((v) => ({
|
||||
name: `${v.name} (Custom)`,
|
||||
value: `custom_${v.google_custom_voice_sid}`,
|
||||
}));
|
||||
setSynthesisGoogleCustomVoiceOptions(customVOices);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updateTtsVoice = (value: string) => {
|
||||
if (shouldUpdateTtsVoice.current) {
|
||||
setSynthVoice(value);
|
||||
shouldUpdateTtsVoice.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
const configRecognizer = () => {
|
||||
getSpeechSupportedLanguagesAndVoices(
|
||||
serviceProviderSid,
|
||||
recogVendor,
|
||||
recogLabel,
|
||||
)
|
||||
.then(({ json }) => {
|
||||
// while fetching data, the user might change the vendor
|
||||
if (currentSttVendor.current !== recogVendor) {
|
||||
return;
|
||||
}
|
||||
// Extract Language
|
||||
const langOpts = json.stt.map((lang) => ({
|
||||
name: lang.name,
|
||||
value: lang.value,
|
||||
}));
|
||||
setRecogLanguageOptions(langOpts);
|
||||
|
||||
/**When vendor is custom, Language is input by user */
|
||||
if (
|
||||
recogVendor.toString() === VENDOR_CUSTOM ||
|
||||
!shouldUpdateSttLanguage.current
|
||||
)
|
||||
return;
|
||||
shouldUpdateSttLanguage.current = false;
|
||||
/** Google and AWS have different language lists */
|
||||
/** If the new language doesn't map then default to "en-US" */
|
||||
const newLang = json.stt.find((lang) => lang.value === recogLang);
|
||||
|
||||
if (
|
||||
(recogVendor === VENDOR_GOOGLE || recogVendor === VENDOR_AWS) &&
|
||||
!newLang
|
||||
) {
|
||||
setRecogLang(LANG_EN_US);
|
||||
} else if (recogVendor === VENDOR_COBALT && !newLang) {
|
||||
setRecogLang(LANG_COBALT_EN_US);
|
||||
} else if (langOpts.length && !newLang) {
|
||||
setRecogLang(langOpts[0].value);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toastError(error.msg);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{synthesis && (
|
||||
<fieldset>
|
||||
<label htmlFor="synthesis_vendor">Speech synthesis vendor</label>
|
||||
<Selector
|
||||
id="synthesis_vendor"
|
||||
name="synthesis_vendor"
|
||||
value={synthVendor}
|
||||
options={ttsVendorOptions.filter(
|
||||
(vendor) =>
|
||||
vendor.value != VENDOR_DEEPGRAM &&
|
||||
vendor.value != VENDOR_SONIOX &&
|
||||
vendor.value !== VENDOR_CUSTOM &&
|
||||
vendor.value !== VENDOR_COBALT
|
||||
)}
|
||||
onChange={(e) => {
|
||||
const vendor = e.target.value as keyof SynthesisVendors;
|
||||
setSynthVendor(vendor);
|
||||
setSynthLabel("");
|
||||
|
||||
/** 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 &&
|
||||
synthLang === LANG_EN_US
|
||||
) {
|
||||
setSynthVoice(LANG_EN_US_STANDARD_C);
|
||||
return;
|
||||
}
|
||||
|
||||
if (vendor === VENDOR_ELEVENLABS) {
|
||||
const newLang = synthesis[vendor].find(
|
||||
(lang) => lang.code === ELEVENLABS_LANG_EN
|
||||
);
|
||||
setSynthLang(ELEVENLABS_LANG_EN);
|
||||
setSynthVoice(newLang!.voices[0].value);
|
||||
return;
|
||||
}
|
||||
|
||||
/** Google and AWS have different language lists */
|
||||
/** If the new language doesn't map then default to "en-US" */
|
||||
let newLang = synthesis[vendor].find(
|
||||
(lang) => lang.code === synthLang
|
||||
);
|
||||
|
||||
if (newLang) {
|
||||
setSynthVoice(newLang.voices[0].value);
|
||||
return;
|
||||
}
|
||||
|
||||
newLang = synthesis[vendor].find(
|
||||
(lang) => lang.code === LANG_EN_US
|
||||
);
|
||||
|
||||
setSynthLang(LANG_EN_US);
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
<fieldset>
|
||||
<label htmlFor="synthesis_vendor">Speech synthesis vendor</label>
|
||||
<Selector
|
||||
id="synthesis_vendor"
|
||||
name="synthesis_vendor"
|
||||
value={synthVendor}
|
||||
options={ttsVendorOptions.filter(
|
||||
(vendor) =>
|
||||
vendor.value != VENDOR_ASSEMBLYAI &&
|
||||
vendor.value != VENDOR_SONIOX &&
|
||||
vendor.value !== VENDOR_CUSTOM &&
|
||||
vendor.value !== VENDOR_COBALT,
|
||||
)}
|
||||
{synthVendor &&
|
||||
!synthVendor.toString().startsWith(VENDOR_CUSTOM) &&
|
||||
synthLang && (
|
||||
<>
|
||||
<label htmlFor="synthesis_lang">Language</label>
|
||||
<Selector
|
||||
id="synthesis_lang"
|
||||
name="synthesis_lang"
|
||||
value={synthLang}
|
||||
options={synthesisLanguageOptions}
|
||||
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;
|
||||
}
|
||||
|
||||
const newLang = synthesis[
|
||||
synthVendor as keyof SynthesisVendors
|
||||
].find((lang) => lang.code === language);
|
||||
|
||||
setSynthVoice(newLang!.voices[0].value);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="synthesis_voice">Voice</label>
|
||||
{synthVendor === VENDOR_MICROSOFT &&
|
||||
selectedCredential &&
|
||||
selectedCredential.use_custom_tts ? (
|
||||
<input
|
||||
id="custom_microsoft_synthesis_voice"
|
||||
type="text"
|
||||
name="custom_microsoft_synthesis_voice"
|
||||
placeholder="Required"
|
||||
required
|
||||
value={synthVoice}
|
||||
onChange={(e) => {
|
||||
setSynthVoice(e.target.value);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Selector
|
||||
id="synthesis_voice"
|
||||
name="synthesis_voice"
|
||||
value={synthVoice}
|
||||
options={synthesisVoiceOptions}
|
||||
onChange={(e) => setSynthVoice(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{synthVendor.toString().startsWith(VENDOR_CUSTOM) && (
|
||||
onChange={(e) => {
|
||||
const vendor = e.target.value as keyof SynthesisVendors;
|
||||
shouldUpdateTtsVoice.current = true;
|
||||
setSynthVendor(vendor);
|
||||
setSynthLabel("");
|
||||
setSynthesisLanguageOptions([]);
|
||||
setSynthesisVoiceOptions([]);
|
||||
}}
|
||||
/>
|
||||
{hasLength(ttsLabelOptions) && (
|
||||
<>
|
||||
<label htmlFor="synthesis_label">Label</label>
|
||||
<Selector
|
||||
id="systhesis_label"
|
||||
name="systhesis_label"
|
||||
value={synthLabel}
|
||||
options={ttsLabelOptions}
|
||||
onChange={(e) => {
|
||||
setSynthLabel(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{synthesisModelOptions && synthVendor === VENDOR_DEEPGRAM && (
|
||||
<>
|
||||
<label htmlFor="synthesis_lang">Model</label>
|
||||
<Selector
|
||||
id="synthesis_voice"
|
||||
name="synthesis_voice"
|
||||
value={synthVoice}
|
||||
options={synthesisModelOptions}
|
||||
onChange={(e) => setSynthVoice(e.target.value)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{synthVendor &&
|
||||
!synthVendor.toString().startsWith(VENDOR_CUSTOM) &&
|
||||
synthVendor !== VENDOR_DEEPGRAM &&
|
||||
synthLang && (
|
||||
<>
|
||||
<label htmlFor="custom_vendor_synthesis_lang">Language</label>
|
||||
<input
|
||||
id="custom_vendor_synthesis_lang"
|
||||
type="text"
|
||||
name="custom_vendor_synthesis_lang"
|
||||
placeholder="Required"
|
||||
required
|
||||
<label htmlFor="synthesis_lang">Language</label>
|
||||
<Selector
|
||||
id="synthesis_lang"
|
||||
name="synthesis_lang"
|
||||
value={synthLang}
|
||||
options={synthesisLanguageOptions}
|
||||
onChange={(e) => {
|
||||
setSynthLang(e.target.value);
|
||||
shouldUpdateTtsVoice.current = true;
|
||||
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;
|
||||
}
|
||||
|
||||
const voices =
|
||||
synthesisSupportedLanguagesAndVoices?.tts.find(
|
||||
(lang) => lang.value === language,
|
||||
)?.voices || [];
|
||||
if (
|
||||
synthVendor === VENDOR_GOOGLE &&
|
||||
synthesisGoogleCustomVoiceOptions &&
|
||||
synthesisGoogleCustomVoiceOptions.length
|
||||
) {
|
||||
setSynthesisVoiceOptions([
|
||||
...synthesisGoogleCustomVoiceOptions,
|
||||
...voices,
|
||||
]);
|
||||
} else {
|
||||
setSynthesisVoiceOptions(voices);
|
||||
}
|
||||
|
||||
setSynthVoice(voices[0].value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<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}
|
||||
onChange={(e) => {
|
||||
setSynthVoice(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</fieldset>
|
||||
)}
|
||||
{recognizers && (
|
||||
<fieldset>
|
||||
<label htmlFor="recognizer_vendor">Speech recognizer vendor</label>
|
||||
<Selector
|
||||
id="recognizer_vendor"
|
||||
name="recognizer_vendor"
|
||||
value={recogVendor}
|
||||
options={sttVendorOptions.filter(
|
||||
(vendor) =>
|
||||
vendor.value != VENDOR_WELLSAID &&
|
||||
vendor.value !== VENDOR_CUSTOM
|
||||
)}
|
||||
onChange={(e) => {
|
||||
const vendor = e.target.value as keyof RecognizerVendors;
|
||||
setRecogVendor(vendor);
|
||||
setRecogLabel("");
|
||||
|
||||
/**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(
|
||||
(lang: Language) => lang.code === recogLang
|
||||
);
|
||||
|
||||
if (
|
||||
(vendor === VENDOR_GOOGLE || vendor === VENDOR_AWS) &&
|
||||
!newLang
|
||||
) {
|
||||
setRecogLang(LANG_EN_US);
|
||||
}
|
||||
// Default colbalt language
|
||||
if (vendor === VENDOR_COBALT) {
|
||||
setRecogLang(LANG_COBALT_EN_US);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{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 && (
|
||||
<>
|
||||
<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,
|
||||
}))}
|
||||
<label htmlFor="synthesis_voice">Voice</label>
|
||||
{synthVendor === VENDOR_MICROSOFT &&
|
||||
selectedCredential &&
|
||||
selectedCredential.use_custom_tts ? (
|
||||
<input
|
||||
id="custom_microsoft_synthesis_voice"
|
||||
type="text"
|
||||
name="custom_microsoft_synthesis_voice"
|
||||
placeholder="Required"
|
||||
required
|
||||
value={synthVoice}
|
||||
onChange={(e) => {
|
||||
setRecogLang(e.target.value);
|
||||
setSynthVoice(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{recogVendor.toString().startsWith(VENDOR_CUSTOM) && (
|
||||
) : (
|
||||
<Selector
|
||||
id="synthesis_voice"
|
||||
name="synthesis_voice"
|
||||
value={synthVoice}
|
||||
options={synthesisVoiceOptions}
|
||||
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="custom_vendor_synthesis_voice">Voice</label>
|
||||
<input
|
||||
id="custom_vendor_synthesis_voice"
|
||||
type="text"
|
||||
name="custom_vendor_synthesis_voice"
|
||||
placeholder="Required"
|
||||
required
|
||||
value={synthVoice}
|
||||
onChange={(e) => {
|
||||
setSynthVoice(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label htmlFor="recognizer_vendor">Speech recognizer vendor</label>
|
||||
<Selector
|
||||
id="recognizer_vendor"
|
||||
name="recognizer_vendor"
|
||||
value={recogVendor}
|
||||
options={sttVendorOptions.filter(
|
||||
(vendor) =>
|
||||
vendor.value != VENDOR_WELLSAID &&
|
||||
vendor.value != VENDOR_ELEVENLABS &&
|
||||
vendor.value != VENDOR_WHISPER &&
|
||||
vendor.value !== VENDOR_CUSTOM,
|
||||
)}
|
||||
onChange={(e) => {
|
||||
const vendor = e.target.value as keyof RecognizerVendors;
|
||||
shouldUpdateSttLanguage.current = true;
|
||||
setRecogVendor(vendor);
|
||||
setRecogLabel("");
|
||||
|
||||
setRecogLanguageOptions([]);
|
||||
}}
|
||||
/>
|
||||
{hasLength(sttLabelOptions) && (
|
||||
<>
|
||||
<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 && (
|
||||
<>
|
||||
<label htmlFor="custom_vendor_recognizer_voice">Language</label>
|
||||
<input
|
||||
id="custom_vendor_recognizer_voice"
|
||||
type="text"
|
||||
name="custom_vendor_recognizer_voice"
|
||||
placeholder="Required"
|
||||
required
|
||||
<label htmlFor="recognizer_lang">Language</label>
|
||||
<Selector
|
||||
id="recognizer_lang"
|
||||
name="recognizer_lang"
|
||||
value={recogLang}
|
||||
options={recogLanguageOptions}
|
||||
onChange={(e) => {
|
||||
setRecogLang(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</fieldset>
|
||||
)}
|
||||
{recogVendor.toString().startsWith(VENDOR_CUSTOM) && (
|
||||
<>
|
||||
<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}
|
||||
onChange={(e) => {
|
||||
setRecogLang(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</fieldset>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,8 +28,8 @@ export const DeleteCarrier = ({
|
||||
if (!ignore) {
|
||||
setPhoneNumbers(
|
||||
json.filter(
|
||||
(phone) => phone.voip_carrier_sid === carrier.voip_carrier_sid
|
||||
)
|
||||
(phone) => phone.voip_carrier_sid === carrier.voip_carrier_sid,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -15,13 +15,13 @@ export const EditCarrier = () => {
|
||||
const params = useParams();
|
||||
const user = useSelectState("user");
|
||||
const [data, refetch, error] = useApiData<Carrier>(
|
||||
`VoipCarriers/${params.voip_carrier_sid}`
|
||||
`VoipCarriers/${params.voip_carrier_sid}`,
|
||||
);
|
||||
const [sipGateways, sipGatewaysRefetch] = useApiData<SipGateway[]>(
|
||||
`SipGateways?voip_carrier_sid=${params.voip_carrier_sid}`
|
||||
`SipGateways?voip_carrier_sid=${params.voip_carrier_sid}`,
|
||||
);
|
||||
const [smppGateways, smppGatewaysRefetch] = useApiData<SmppGateway[]>(
|
||||
`SmppGateways?voip_carrier_sid=${params.voip_carrier_sid}`
|
||||
`SmppGateways?voip_carrier_sid=${params.voip_carrier_sid}`,
|
||||
);
|
||||
|
||||
useScopedRedirect(
|
||||
@@ -29,7 +29,7 @@ export const EditCarrier = () => {
|
||||
ROUTE_INTERNAL_CARRIERS,
|
||||
user,
|
||||
"You do not have access to this resource",
|
||||
data
|
||||
data,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -236,7 +236,7 @@ export const CarrierForm = ({
|
||||
const updateSipGateways = (
|
||||
index: number,
|
||||
key: string,
|
||||
value: typeof sipGateways[number][keyof SipGateway]
|
||||
value: (typeof sipGateways)[number][keyof SipGateway],
|
||||
) => {
|
||||
setSipGateways(
|
||||
sipGateways.map((g, i) =>
|
||||
@@ -251,18 +251,18 @@ export const CarrierForm = ({
|
||||
getIpValidationType(value) === IP &&
|
||||
g.port === null && { port: 5060 }),
|
||||
}
|
||||
: g
|
||||
)
|
||||
: g,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const updateSmppGateways = (
|
||||
index: number,
|
||||
key: string,
|
||||
value: typeof smppGateways[number][keyof SmppGateway]
|
||||
value: (typeof smppGateways)[number][keyof SmppGateway],
|
||||
) => {
|
||||
setSmppGateways(
|
||||
smppGateways.map((g, i) => (i === index ? { ...g, [key]: value } : g))
|
||||
smppGateways.map((g, i) => (i === index ? { ...g, [key]: value } : g)),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -271,8 +271,8 @@ export const CarrierForm = ({
|
||||
sipGateways.map(({ sip_gateway_sid, ...g }: SipGateway) =>
|
||||
sip_gateway_sid
|
||||
? putSipGateway(sip_gateway_sid, g)
|
||||
: postSipGateway({ ...g, voip_carrier_sid })
|
||||
)
|
||||
: postSipGateway({ ...g, voip_carrier_sid }),
|
||||
),
|
||||
).then(() => {
|
||||
if (carrierSipGateways) {
|
||||
carrierSipGateways.refetch();
|
||||
@@ -289,7 +289,7 @@ export const CarrierForm = ({
|
||||
smpp_gateway_sid
|
||||
? putSmppGateway(smpp_gateway_sid, g)
|
||||
: postSmppGateway({ ...g, voip_carrier_sid });
|
||||
})
|
||||
}),
|
||||
).then(() => {
|
||||
if (carrierSmppGateways) {
|
||||
carrierSmppGateways.refetch();
|
||||
@@ -300,7 +300,7 @@ export const CarrierForm = ({
|
||||
const handleSipGatewayDelete = (g?: SipGateway) => {
|
||||
if (g && g.sip_gateway_sid) {
|
||||
deleteSipGateway(g.sip_gateway_sid).then(() =>
|
||||
toastSuccess("SIP gateway successfully deleted")
|
||||
toastSuccess("SIP gateway successfully deleted"),
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -311,8 +311,8 @@ export const CarrierForm = ({
|
||||
toastSuccess(
|
||||
`SMPP ${
|
||||
g.outbound ? "outbound" : "inbound"
|
||||
} gateway successfully deleted`
|
||||
)
|
||||
} gateway successfully deleted`,
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -336,10 +336,13 @@ export const CarrierForm = ({
|
||||
const gateway = sipGateways[i];
|
||||
const type = getIpValidationType(gateway.ipv4);
|
||||
|
||||
/** DH: unclear why we had this restriction, removing for now
|
||||
if (type === FQDN_TOP_LEVEL) {
|
||||
refSipIp.current[i].focus();
|
||||
return "When using an FQDN, you must use a subdomain (e.g. sip.example.com).";
|
||||
} else if (type === FQDN && (!gateway.outbound || gateway.inbound)) {
|
||||
*/
|
||||
if (type === FQDN && (!gateway.outbound || gateway.inbound)) {
|
||||
refSipIp.current[i].focus();
|
||||
return "A fully qualified domain name may only be used for outbound calls.";
|
||||
} else if (type === INVALID) {
|
||||
@@ -426,7 +429,7 @@ export const CarrierForm = ({
|
||||
|
||||
const emptySipIp = sipGateways.find((g) => g.ipv4.trim() === "");
|
||||
const invalidSipPort = sipGateways.find(
|
||||
(g) => hasValue(g.port) && !isValidPort(g.port)
|
||||
(g) => hasValue(g.port) && !isValidPort(g.port),
|
||||
);
|
||||
const sipGatewayValidation = getSipValidation();
|
||||
|
||||
@@ -527,7 +530,7 @@ export const CarrierForm = ({
|
||||
putCarrier(
|
||||
currentServiceProvider.service_provider_sid,
|
||||
carrier.data.voip_carrier_sid,
|
||||
carrierPayload
|
||||
carrierPayload,
|
||||
)
|
||||
.then(() => {
|
||||
if (carrier.data?.voip_carrier_sid) {
|
||||
@@ -538,7 +541,7 @@ export const CarrierForm = ({
|
||||
toastSuccess("Carrier updated successfully");
|
||||
carrier.refetch();
|
||||
navigate(
|
||||
`${ROUTE_INTERNAL_CARRIERS}/${carrier.data?.voip_carrier_sid}/edit`
|
||||
`${ROUTE_INTERNAL_CARRIERS}/${carrier.data?.voip_carrier_sid}/edit`,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -568,7 +571,7 @@ export const CarrierForm = ({
|
||||
setLocation();
|
||||
if (predefinedName && hasLength(predefinedCarriers)) {
|
||||
const predefinedCarrierSid = predefinedCarriers.find(
|
||||
(a) => a.name === predefinedName
|
||||
(a) => a.name === predefinedName,
|
||||
)?.predefined_carrier_sid;
|
||||
|
||||
if (currentServiceProvider && predefinedCarrierSid) {
|
||||
@@ -576,11 +579,11 @@ export const CarrierForm = ({
|
||||
user?.scope === USER_ACCOUNT
|
||||
? postPredefinedCarrierTemplateAccount(
|
||||
accountSid,
|
||||
predefinedCarrierSid
|
||||
predefinedCarrierSid,
|
||||
)
|
||||
: postPredefinedCarrierTemplate(
|
||||
currentServiceProvider.service_provider_sid,
|
||||
predefinedCarrierSid
|
||||
predefinedCarrierSid,
|
||||
);
|
||||
|
||||
postPredefinedCarrier
|
||||
@@ -636,7 +639,12 @@ export const CarrierForm = ({
|
||||
|
||||
return (
|
||||
<Section slim>
|
||||
<form className="form form--internal" onSubmit={handleSubmit}>
|
||||
<form
|
||||
className={`form form--internal ${
|
||||
!carrier?.data && carrier?.refetch ? "form--blur" : ""
|
||||
}`}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<fieldset>
|
||||
<MS>{MSG_REQUIRED_FIELDS}</MS>
|
||||
</fieldset>
|
||||
@@ -685,9 +693,9 @@ export const CarrierForm = ({
|
||||
(carrier: PredefinedCarrier) => ({
|
||||
name: carrier.name,
|
||||
value: carrier.name,
|
||||
})
|
||||
}),
|
||||
)
|
||||
: []
|
||||
: [],
|
||||
)}
|
||||
onChange={(e) => setPredefinedName(e.target.value)}
|
||||
/>
|
||||
@@ -740,7 +748,7 @@ export const CarrierForm = ({
|
||||
accounts={
|
||||
user?.scope === USER_ACCOUNT
|
||||
? accounts?.filter(
|
||||
(acct) => user.account_sid === acct.account_sid
|
||||
(acct) => user.account_sid === acct.account_sid,
|
||||
)
|
||||
: accounts
|
||||
}
|
||||
@@ -752,8 +760,8 @@ export const CarrierForm = ({
|
||||
user?.scope !== USER_ACCOUNT
|
||||
? false
|
||||
: user.account_sid !== accountSid
|
||||
? true
|
||||
: false
|
||||
? true
|
||||
: false
|
||||
}
|
||||
/>
|
||||
{user &&
|
||||
@@ -766,7 +774,7 @@ export const CarrierForm = ({
|
||||
defaultOption="None"
|
||||
application={[applicationSid, setApplicationSid]}
|
||||
applications={applications.filter(
|
||||
(application) => application.account_sid === accountSid
|
||||
(application) => application.account_sid === accountSid,
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
@@ -994,7 +1002,7 @@ export const CarrierForm = ({
|
||||
!isNotBlank(e.target.value) &&
|
||||
getIpValidationType(g.ipv4) !== IP
|
||||
? null
|
||||
: Number(e.target.value)
|
||||
: Number(e.target.value),
|
||||
);
|
||||
}}
|
||||
ref={(ref: HTMLInputElement) =>
|
||||
@@ -1007,7 +1015,6 @@ export const CarrierForm = ({
|
||||
<Selector
|
||||
id={`sip_protocol_${i}`}
|
||||
name={`sip_protocol${i}`}
|
||||
placeholder=""
|
||||
value={g.protocol}
|
||||
options={SIP_GATEWAY_PROTOCOL_OPTIONS}
|
||||
onChange={(e) => {
|
||||
@@ -1020,7 +1027,6 @@ export const CarrierForm = ({
|
||||
<Selector
|
||||
id={`sip_netmask_${i}`}
|
||||
name={`sip_netmask${i}`}
|
||||
placeholder="32"
|
||||
value={g.netmask}
|
||||
options={NETMASK_OPTIONS}
|
||||
onChange={(e) => {
|
||||
@@ -1045,7 +1051,7 @@ export const CarrierForm = ({
|
||||
updateSipGateways(
|
||||
i,
|
||||
"is_active",
|
||||
e.target.checked ? 1 : 0
|
||||
e.target.checked ? 1 : 0,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
@@ -1064,7 +1070,7 @@ export const CarrierForm = ({
|
||||
updateSipGateways(
|
||||
i,
|
||||
"inbound",
|
||||
e.target.checked ? 1 : 0
|
||||
e.target.checked ? 1 : 0,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
@@ -1083,7 +1089,7 @@ export const CarrierForm = ({
|
||||
updateSipGateways(
|
||||
i,
|
||||
"outbound",
|
||||
e.target.checked
|
||||
e.target.checked,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
@@ -1105,7 +1111,7 @@ export const CarrierForm = ({
|
||||
updateSipGateways(
|
||||
i,
|
||||
"pad_crypto",
|
||||
e.target.checked
|
||||
e.target.checked,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
@@ -1113,6 +1119,53 @@ export const CarrierForm = ({
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
{Boolean(g.outbound) && (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={`send_options_ping_${i}`}
|
||||
className="chk"
|
||||
>
|
||||
<input
|
||||
id={`send_options_ping_${i}`}
|
||||
name={`send_options_ping_${i}`}
|
||||
type="checkbox"
|
||||
checked={g.send_options_ping ? true : false}
|
||||
onChange={(e) => {
|
||||
updateSipGateways(
|
||||
i,
|
||||
"send_options_ping",
|
||||
e.target.checked,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div>Send OPTIONS ping</div>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
{Boolean(g.outbound) &&
|
||||
(g.protocol === "tls" || g.protocol === "tls/srtp") && (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={`use_sips_scheme_${i}`}
|
||||
className="chk"
|
||||
>
|
||||
<input
|
||||
id={`use_sips_scheme_${i}`}
|
||||
name={`use_sips_scheme_${i}`}
|
||||
type="checkbox"
|
||||
checked={g.use_sips_scheme ? true : false}
|
||||
onChange={(e) => {
|
||||
updateSipGateways(
|
||||
i,
|
||||
"use_sips_scheme",
|
||||
e.target.checked,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div>Use sips scheme</div>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -1124,15 +1177,15 @@ export const CarrierForm = ({
|
||||
|
||||
if (sipGateways.length === 1) {
|
||||
setSipMessage(
|
||||
"You must provide at least one SIP Gateway."
|
||||
"You must provide at least one SIP Gateway.",
|
||||
);
|
||||
} else {
|
||||
handleSipGatewayDelete(
|
||||
sipGateways.find((g2, i2) => i2 === i)
|
||||
sipGateways.find((g2, i2) => i2 === i),
|
||||
);
|
||||
|
||||
setSipGateways(
|
||||
sipGateways.filter((g2, i2) => i2 !== i)
|
||||
sipGateways.filter((g2, i2) => i2 !== i),
|
||||
);
|
||||
}
|
||||
}}
|
||||
@@ -1246,7 +1299,7 @@ export const CarrierForm = ({
|
||||
updateSmppGateways(
|
||||
i,
|
||||
"port",
|
||||
Number(e.target.value)
|
||||
Number(e.target.value),
|
||||
)
|
||||
}
|
||||
ref={(ref: HTMLInputElement) =>
|
||||
@@ -1265,7 +1318,7 @@ export const CarrierForm = ({
|
||||
updateSmppGateways(
|
||||
i,
|
||||
"use_tls",
|
||||
e.target.checked
|
||||
e.target.checked,
|
||||
)
|
||||
}
|
||||
/>
|
||||
@@ -1287,15 +1340,15 @@ export const CarrierForm = ({
|
||||
(smppSystemId || smppPass)
|
||||
) {
|
||||
setSmppOutboundMessage(
|
||||
"You must provide at least one Outbound Gateway."
|
||||
"You must provide at least one Outbound Gateway.",
|
||||
);
|
||||
} else {
|
||||
handleSmppGatewayDelete(
|
||||
smppGateways.find((g2, i2) => i2 === i)
|
||||
smppGateways.find((g2, i2) => i2 === i),
|
||||
);
|
||||
|
||||
setSmppGateways(
|
||||
smppGateways.filter((g2, i2) => i2 !== i)
|
||||
smppGateways.filter((g2, i2) => i2 !== i),
|
||||
);
|
||||
}
|
||||
}}
|
||||
@@ -1389,7 +1442,6 @@ export const CarrierForm = ({
|
||||
<Selector
|
||||
id={`smpp_netmask_${i}`}
|
||||
name={`smpp_netmask_${i}`}
|
||||
placeholder="32"
|
||||
options={NETMASK_OPTIONS}
|
||||
value={g.netmask}
|
||||
onChange={(e) =>
|
||||
@@ -1404,11 +1456,11 @@ export const CarrierForm = ({
|
||||
type="button"
|
||||
onClick={() => {
|
||||
handleSmppGatewayDelete(
|
||||
smppGateways.find((g2, i2) => i2 === i)
|
||||
smppGateways.find((g2, i2) => i2 === i),
|
||||
);
|
||||
|
||||
setSmppGateways(
|
||||
smppGateways.filter((g2, i2) => i2 !== i)
|
||||
smppGateways.filter((g2, i2) => i2 !== i),
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -12,7 +12,7 @@ type GatewaysProps = {
|
||||
|
||||
export const Gateways = ({ carrier }: GatewaysProps) => {
|
||||
const [gateways, , error] = useApiData<SipGateway[]>(
|
||||
`SipGateways?voip_carrier_sid=${carrier.voip_carrier_sid}`
|
||||
`SipGateways?voip_carrier_sid=${carrier.voip_carrier_sid}`,
|
||||
);
|
||||
|
||||
const renderGateways = () => {
|
||||
|
||||
@@ -66,14 +66,14 @@ export const Carriers = () => {
|
||||
? carriers.filter((carrier) =>
|
||||
accountSid
|
||||
? carrier.account_sid === accountSid
|
||||
: carrier.account_sid === null
|
||||
: carrier.account_sid === null,
|
||||
)
|
||||
: [];
|
||||
}, [accountSid, carrier, carriers]);
|
||||
|
||||
const filteredCarriers = useFilteredResults<Carrier>(
|
||||
filter,
|
||||
carriersFiltered
|
||||
carriersFiltered,
|
||||
);
|
||||
|
||||
const handleDelete = () => {
|
||||
@@ -87,10 +87,10 @@ export const Carriers = () => {
|
||||
.then(() => {
|
||||
Promise.all([
|
||||
getFetch<SipGateway[]>(
|
||||
`${API_SIP_GATEWAY}?voip_carrier_sid=${carrier.voip_carrier_sid}`
|
||||
`${API_SIP_GATEWAY}?voip_carrier_sid=${carrier.voip_carrier_sid}`,
|
||||
),
|
||||
getFetch<SmppGateway[]>(
|
||||
`${API_SMPP_GATEWAY}?voip_carrier_sid=${carrier.voip_carrier_sid}`
|
||||
`${API_SMPP_GATEWAY}?voip_carrier_sid=${carrier.voip_carrier_sid}`,
|
||||
),
|
||||
]).then(([sipGatewaysRes, smppGatewaysRes]) => {
|
||||
hasLength(sipGatewaysRes.json) &&
|
||||
@@ -99,8 +99,8 @@ export const Carriers = () => {
|
||||
g &&
|
||||
g.sip_gateway_sid &&
|
||||
deleteSipGateway(g.sip_gateway_sid).catch((error) =>
|
||||
toastError(error.msg)
|
||||
)
|
||||
toastError(error.msg),
|
||||
),
|
||||
);
|
||||
hasLength(smppGatewaysRes.json) &&
|
||||
smppGatewaysRes.json.forEach(
|
||||
@@ -108,8 +108,8 @@ export const Carriers = () => {
|
||||
g &&
|
||||
g.smpp_gateway_sid &&
|
||||
deleteSmppGateway(g.smpp_gateway_sid).catch((error) =>
|
||||
toastError(error.msg)
|
||||
)
|
||||
toastError(error.msg),
|
||||
),
|
||||
);
|
||||
});
|
||||
setCarrier(null);
|
||||
@@ -117,7 +117,7 @@ export const Carriers = () => {
|
||||
toastSuccess(
|
||||
<>
|
||||
Deleted Carrier <strong>{carrier.name}</strong>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -130,7 +130,7 @@ export const Carriers = () => {
|
||||
setLocation();
|
||||
if (currentServiceProvider) {
|
||||
setApiUrl(
|
||||
`ServiceProviders/${currentServiceProvider.service_provider_sid}/VoipCarriers`
|
||||
`ServiceProviders/${currentServiceProvider.service_provider_sid}/VoipCarriers`,
|
||||
);
|
||||
}
|
||||
}, [user, currentServiceProvider, accountSid]);
|
||||
@@ -155,7 +155,7 @@ export const Carriers = () => {
|
||||
</Icon>
|
||||
</Link>
|
||||
</section>
|
||||
<section className="filters filters--spaced">
|
||||
<section className="filters filters--multi">
|
||||
<SearchFilter
|
||||
placeholder="Filter carriers"
|
||||
filter={[filter, setFilter]}
|
||||
|
||||
@@ -10,6 +10,13 @@ type CarrierProps = {
|
||||
};
|
||||
|
||||
export const RegisterStatus = ({ carrier }: CarrierProps) => {
|
||||
const getReason = () => {
|
||||
return carrier.register_status.reason
|
||||
? typeof carrier.register_status.reason === "string"
|
||||
? carrier.register_status.reason
|
||||
: "Not Started"
|
||||
: "Not Started";
|
||||
};
|
||||
const renderStatus = () => {
|
||||
return (
|
||||
<div
|
||||
@@ -20,7 +27,7 @@ export const RegisterStatus = ({ carrier }: CarrierProps) => {
|
||||
: "jam"
|
||||
: "jean"
|
||||
}`}
|
||||
title={carrier.register_status.reason || "Not Started"}
|
||||
title={getReason()}
|
||||
>
|
||||
{carrier.register_status.status === CARRIER_REG_OK ? (
|
||||
<Icons.CheckCircle />
|
||||
@@ -40,8 +47,7 @@ export const RegisterStatus = ({ carrier }: CarrierProps) => {
|
||||
<details className={carrier.register_status.status || "not-tested"}>
|
||||
<summary>{renderStatus()}</summary>
|
||||
<MS>
|
||||
<strong>Reason:</strong>{" "}
|
||||
{carrier.register_status.reason || "Not Started"}
|
||||
<strong>Reason:</strong> {getReason()}
|
||||
</MS>
|
||||
<PcapButton
|
||||
accountSid={carrier.account_sid || ""}
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
import { H1 } from "@jambonz/ui-kit";
|
||||
import React, { useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useApiData } from "src/api";
|
||||
import { Client } from "src/api/types";
|
||||
import { toastError } from "src/store";
|
||||
import ClientsForm from "./form";
|
||||
import { ROUTE_INTERNAL_CLIENTS } from "src/router/routes";
|
||||
|
||||
export const ClientsEdit = () => {
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [data, refetch, error] = useApiData<Client>(
|
||||
`Clients/${params.client_sid}`
|
||||
`Clients/${params.client_sid}`,
|
||||
);
|
||||
|
||||
/** Handle error toast at top level... */
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
toastError(error.msg);
|
||||
navigate(ROUTE_INTERNAL_CLIENTS);
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from "src/api";
|
||||
import { USER_ACCOUNT } from "src/api/constants";
|
||||
import { Account, Client, UseApiDataMap } from "src/api/types";
|
||||
import { Section } from "src/components";
|
||||
import { Section, Tooltip } from "src/components";
|
||||
import { AccountSelect, Message, Passwd } from "src/components/forms";
|
||||
import { MSG_REQUIRED_FIELDS } from "src/constants";
|
||||
import { ROUTE_INTERNAL_CLIENTS } from "src/router/routes";
|
||||
@@ -30,7 +30,12 @@ export const ClientsForm = ({ client }: ClientsFormProps) => {
|
||||
const [accountSid, setAccountSid] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [isActive, setIsActive] = useState(true);
|
||||
const [isActive, setIsActive] = useState(
|
||||
client ? client.data?.is_active : true,
|
||||
);
|
||||
const [allowDirectAppCalling, setAllowDirectAppCalling] = useState(true);
|
||||
const [allowDirectQueueCalling, setAllowDirectQueueCalling] = useState(true);
|
||||
const [allowDirectUserCalling, setAllowDirectUserCalling] = useState(true);
|
||||
const [modal, setModal] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
@@ -42,6 +47,9 @@ export const ClientsForm = ({ client }: ClientsFormProps) => {
|
||||
username: username,
|
||||
password: password,
|
||||
is_active: isActive,
|
||||
allow_direct_app_calling: allowDirectAppCalling,
|
||||
allow_direct_queue_calling: allowDirectQueueCalling,
|
||||
allow_direct_user_calling: allowDirectUserCalling,
|
||||
})
|
||||
.then(() => {
|
||||
toastSuccess("Client created successfully");
|
||||
@@ -52,10 +60,10 @@ export const ClientsForm = ({ client }: ClientsFormProps) => {
|
||||
});
|
||||
} else {
|
||||
putClient(client.data?.client_sid || "", {
|
||||
account_sid: accountSid,
|
||||
username: username,
|
||||
...(password && { password: password }),
|
||||
is_active: isActive,
|
||||
allow_direct_app_calling: allowDirectAppCalling,
|
||||
allow_direct_queue_calling: allowDirectQueueCalling,
|
||||
allow_direct_user_calling: allowDirectUserCalling,
|
||||
})
|
||||
.then(() => {
|
||||
toastSuccess("Client updated successfully");
|
||||
@@ -99,6 +107,9 @@ export const ClientsForm = ({ client }: ClientsFormProps) => {
|
||||
}
|
||||
|
||||
setIsActive(client.data.is_active);
|
||||
setAllowDirectAppCalling(client.data.allow_direct_app_calling);
|
||||
setAllowDirectQueueCalling(client.data.allow_direct_queue_calling);
|
||||
setAllowDirectUserCalling(client.data.allow_direct_user_calling);
|
||||
}
|
||||
}, [client]);
|
||||
|
||||
@@ -107,8 +118,6 @@ export const ClientsForm = ({ client }: ClientsFormProps) => {
|
||||
if (!accountSid || !accounts || !acc) return;
|
||||
if (!acc?.sip_realm) {
|
||||
setErrorMessage(`Sip realm is not set for the account.`);
|
||||
} else if (!acc?.device_calling_application_sid) {
|
||||
setErrorMessage(`Device calling application is not set for the account.`);
|
||||
} else {
|
||||
setErrorMessage("");
|
||||
}
|
||||
@@ -116,7 +125,12 @@ export const ClientsForm = ({ client }: ClientsFormProps) => {
|
||||
return (
|
||||
<>
|
||||
<Section slim>
|
||||
<form className="form form--internal" onSubmit={handleSubmit}>
|
||||
<form
|
||||
className={`form form--internal ${
|
||||
!client?.data && client?.refetch ? "form--blur" : ""
|
||||
}`}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<fieldset>
|
||||
<MS>{MSG_REQUIRED_FIELDS}</MS>
|
||||
{errorMessage && <Message message={errorMessage} />}
|
||||
@@ -134,22 +148,12 @@ export const ClientsForm = ({ client }: ClientsFormProps) => {
|
||||
placeholder="user name"
|
||||
value={username}
|
||||
required={true}
|
||||
disabled={hasValue(client)}
|
||||
autoComplete="off"
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<label htmlFor="is_active" className="chk">
|
||||
<input
|
||||
id="is_active"
|
||||
name="is_active"
|
||||
type="checkbox"
|
||||
checked={isActive}
|
||||
onChange={(e) => setIsActive(e.target.checked)}
|
||||
/>
|
||||
<div>Active</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label htmlFor="password">
|
||||
Password{!hasValue(client) && <span>*</span>}
|
||||
</label>
|
||||
@@ -160,14 +164,67 @@ export const ClientsForm = ({ client }: ClientsFormProps) => {
|
||||
value={password}
|
||||
placeholder="Password"
|
||||
setValue={setPassword}
|
||||
disabled={hasValue(client)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label htmlFor="is_active" className="chk">
|
||||
<input
|
||||
id="is_active"
|
||||
name="is_active"
|
||||
type="checkbox"
|
||||
checked={isActive}
|
||||
onChange={(e) => setIsActive(e.target.checked)}
|
||||
/>
|
||||
<div>Active</div>
|
||||
</label>
|
||||
<label htmlFor="allow_direct_app_calling" className="chk">
|
||||
<input
|
||||
id="allow_direct_app_calling"
|
||||
name="allow_direct_app_calling"
|
||||
type="checkbox"
|
||||
checked={allowDirectAppCalling}
|
||||
onChange={(e) => setAllowDirectAppCalling(e.target.checked)}
|
||||
/>
|
||||
<div>Allow direct calling to applications</div>
|
||||
<Tooltip text="Allow user to call applications without configuring an application for sip device calls.">
|
||||
{" "}
|
||||
</Tooltip>
|
||||
</label>
|
||||
<label htmlFor="allow_direct_queue_calling" className="chk">
|
||||
<input
|
||||
id="allow_direct_queue_calling"
|
||||
name="allow_direct_queue_calling"
|
||||
type="checkbox"
|
||||
checked={allowDirectQueueCalling}
|
||||
onChange={(e) => setAllowDirectQueueCalling(e.target.checked)}
|
||||
/>
|
||||
<div>Allow direct calling to queues</div>
|
||||
<Tooltip text="Allow user to take calls from queues without configuring an application for sip device calls.">
|
||||
{" "}
|
||||
</Tooltip>
|
||||
</label>
|
||||
<label htmlFor="allow_direct_user_calling" className="chk">
|
||||
<input
|
||||
id="allow_direct_user_calling"
|
||||
name="allow_direct_user_calling"
|
||||
type="checkbox"
|
||||
checked={allowDirectUserCalling}
|
||||
onChange={(e) => setAllowDirectUserCalling(e.target.checked)}
|
||||
/>
|
||||
<div>Allow direct calling to other users</div>
|
||||
<Tooltip text="Allow user to call other users without configuring an application for sip device calls.">
|
||||
{" "}
|
||||
</Tooltip>
|
||||
</label>
|
||||
</fieldset>
|
||||
{user?.scope !== USER_ACCOUNT && (
|
||||
<fieldset>
|
||||
<AccountSelect
|
||||
accounts={accounts}
|
||||
account={[accountSid, setAccountSid]}
|
||||
label="Used by"
|
||||
label="Belongs to"
|
||||
required={true}
|
||||
defaultOption={false}
|
||||
disabled={hasValue(client)}
|
||||
|
||||
@@ -40,7 +40,7 @@ export const Clients = () => {
|
||||
setSelectedAccount(
|
||||
accountSid
|
||||
? accounts?.find((a: Account) => a.account_sid === accountSid)
|
||||
: null
|
||||
: null,
|
||||
);
|
||||
|
||||
return clients
|
||||
@@ -48,8 +48,8 @@ export const Clients = () => {
|
||||
return accountSid
|
||||
? c.account_sid === accountSid
|
||||
: accounts
|
||||
? accounts.map((a) => a.account_sid).includes(c.account_sid || "")
|
||||
: false;
|
||||
? accounts.map((a) => a.account_sid).includes(c.account_sid || "")
|
||||
: false;
|
||||
})
|
||||
: [];
|
||||
}, [accountSid, clients, accounts]);
|
||||
@@ -63,7 +63,7 @@ export const Clients = () => {
|
||||
toastSuccess(
|
||||
<>
|
||||
Deleted sip client <strong>{client.username}</strong>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
setClient(null);
|
||||
refetch();
|
||||
@@ -126,7 +126,7 @@ export const Clients = () => {
|
||||
</Link>
|
||||
</section>
|
||||
|
||||
<section className="filters filters--spaced">
|
||||
<section className="filters filters--multi">
|
||||
<SearchFilter
|
||||
placeholder="Filter clients"
|
||||
filter={[filter, setFilter]}
|
||||
@@ -179,7 +179,7 @@ export const Clients = () => {
|
||||
<span>
|
||||
{
|
||||
accounts?.find(
|
||||
(acct) => acct.account_sid === c.account_sid
|
||||
(acct) => acct.account_sid === c.account_sid,
|
||||
)?.name
|
||||
}
|
||||
</span>
|
||||
|
||||
@@ -27,7 +27,7 @@ type CardProps = {
|
||||
index1: number,
|
||||
index2: number,
|
||||
key: string,
|
||||
value: unknown
|
||||
value: unknown,
|
||||
) => void;
|
||||
handleRouteDelete: (lr: LcrRoute, index: number) => void;
|
||||
carrierSelectorOptions: SelectorOption[];
|
||||
@@ -141,7 +141,6 @@ export const Card = ({
|
||||
<Selector
|
||||
id={`lcr_carrier_set_entry_carrier_${index}`}
|
||||
name={`lcr_carrier_set_entry_carrier_${index}`}
|
||||
placeholder="Carrier"
|
||||
value={
|
||||
lr.lcr_carrier_set_entries && lr.lcr_carrier_set_entries.length > 0
|
||||
? lr.lcr_carrier_set_entries[0].voip_carrier_sid
|
||||
@@ -156,7 +155,7 @@ export const Card = ({
|
||||
index,
|
||||
0,
|
||||
"voip_carrier_sid",
|
||||
e.target.value
|
||||
e.target.value,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -6,7 +6,6 @@ import update from "immutability-helper";
|
||||
import { deleteLcrRoute } from "src/api";
|
||||
import { toastError, toastSuccess } from "src/store";
|
||||
import { SelectorOption } from "src/components/forms/selector";
|
||||
import { NOT_AVAILABLE_PREFIX } from "src/constants";
|
||||
|
||||
type ContainerProps = {
|
||||
lcrRoute: [LcrRoute[], React.Dispatch<React.SetStateAction<LcrRoute[]>>];
|
||||
@@ -24,13 +23,13 @@ export const Container = ({
|
||||
[dragIndex, 1],
|
||||
[hoverIndex, 0, prevCards[dragIndex]],
|
||||
],
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const updateLcrRoute = (index: number, key: string, value: unknown) => {
|
||||
setLcrRoutes(
|
||||
lcrRoutes.map((lr, i) => (i === index ? { ...lr, [key]: value } : lr))
|
||||
lcrRoutes.map((lr, i) => (i === index ? { ...lr, [key]: value } : lr)),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -38,7 +37,7 @@ export const Container = ({
|
||||
index1: number,
|
||||
index2: number,
|
||||
key: string,
|
||||
value: unknown
|
||||
value: unknown,
|
||||
) => {
|
||||
setLcrRoutes(
|
||||
lcrRoutes.map((lr, i) =>
|
||||
@@ -52,20 +51,16 @@ export const Container = ({
|
||||
...entry,
|
||||
[key]: value,
|
||||
}
|
||||
: entry
|
||||
: entry,
|
||||
),
|
||||
}
|
||||
: lr
|
||||
)
|
||||
: lr,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const handleRouteDelete = (r: LcrRoute | undefined, index: number) => {
|
||||
if (
|
||||
r &&
|
||||
r.lcr_route_sid &&
|
||||
!r.lcr_route_sid.startsWith(NOT_AVAILABLE_PREFIX)
|
||||
) {
|
||||
if (r && r.lcr_route_sid) {
|
||||
deleteLcrRoute(r.lcr_route_sid)
|
||||
.then(() => {
|
||||
toastSuccess("Least cost routing rule successfully deleted");
|
||||
@@ -82,7 +77,7 @@ export const Container = ({
|
||||
{hasLength(lcrRoutes) &&
|
||||
lcrRoutes.map((lr, i) => (
|
||||
<Card
|
||||
key={lr.lcr_route_sid}
|
||||
key={lr.lcr_route_sid || i}
|
||||
lr={lr}
|
||||
index={i}
|
||||
moveCard={moveCard}
|
||||
|
||||
@@ -7,10 +7,10 @@ import { useParams } from "react-router-dom";
|
||||
export const EditLcr = () => {
|
||||
const params = useParams();
|
||||
const [lcrData, lcrRefect, lcrError] = useApiData<Lcr>(
|
||||
`Lcrs/${params.lcr_sid}`
|
||||
`Lcrs/${params.lcr_sid}`,
|
||||
);
|
||||
const [lcrRouteData, lcrRouteRefect, lcrRouteError] = useApiData<LcrRoute[]>(
|
||||
`LcrRoutes?lcr_sid=${params.lcr_sid}`
|
||||
`LcrRoutes?lcr_sid=${params.lcr_sid}`,
|
||||
);
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -8,37 +8,33 @@ import {
|
||||
useDispatch,
|
||||
useSelectState,
|
||||
} from "src/store";
|
||||
import { MSG_REQUIRED_FIELDS, NOT_AVAILABLE_PREFIX } from "src/constants";
|
||||
import { MSG_REQUIRED_FIELDS } from "src/constants";
|
||||
import { setLocation } from "src/store/localStore";
|
||||
import { AccountSelect, Message, Selector } from "src/components/forms";
|
||||
import type {
|
||||
Account,
|
||||
Carrier,
|
||||
Lcr,
|
||||
LcrCarrierSetEntry,
|
||||
LcrRoute,
|
||||
UseApiDataMap,
|
||||
} from "src/api/types";
|
||||
import { ROUTE_INTERNAL_LEST_COST_ROUTING } from "src/router/routes";
|
||||
import {
|
||||
deleteLcr,
|
||||
postLcrCarrierSetEntry,
|
||||
putLcrCarrierSetEntries,
|
||||
putLcrRoutes,
|
||||
putLcr,
|
||||
postLcrCreateRoutes,
|
||||
putLcrUpdateRoutes,
|
||||
useApiData,
|
||||
useServiceProviderData,
|
||||
getLcrRoute,
|
||||
} from "src/api";
|
||||
import { USER_ACCOUNT, USER_ADMIN } from "src/api/constants";
|
||||
import { postLcr } from "src/api";
|
||||
import { postLcrRoute } from "src/api";
|
||||
import DeleteLcr from "./delete";
|
||||
import { Scope } from "src/store/types";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import Container from "./container";
|
||||
import { v4 } from "uuid";
|
||||
import { hasValue } from "src/utils";
|
||||
|
||||
type LcrFormProps = {
|
||||
lcrDataMap?: UseApiDataMap<Lcr>;
|
||||
@@ -47,7 +43,7 @@ type LcrFormProps = {
|
||||
|
||||
export const LcrForm = ({ lcrDataMap, lcrRouteDataMap }: LcrFormProps) => {
|
||||
const LCR_ROUTE_TEMPLATE: LcrRoute = {
|
||||
lcr_route_sid: `${NOT_AVAILABLE_PREFIX}${v4()}`,
|
||||
lcr_route_sid: "",
|
||||
regex: "",
|
||||
lcr_sid: "",
|
||||
priority: 0,
|
||||
@@ -68,7 +64,7 @@ export const LcrForm = ({ lcrDataMap, lcrRouteDataMap }: LcrFormProps) => {
|
||||
const [defaultLcrCarrier, setDefaultLcrCarrier] = useState("");
|
||||
const [defaultLcrCarrierSetEntrySid, setDefaultLcrCarrierSetEntrySid] =
|
||||
useState<string | null>();
|
||||
const [defaultLcrRouteSid, setDefaultLcrRouteSid] = useState("");
|
||||
const [defaultLcrRoute, setDefaultLcrRoute] = useState<LcrRoute | null>(null);
|
||||
const [defaultCarrier, setDefaultCarrier] = useState("");
|
||||
const [apiUrl, setApiUrl] = useState("");
|
||||
const [accountSid, setAccountSid] = useState("");
|
||||
@@ -89,7 +85,7 @@ export const LcrForm = ({ lcrDataMap, lcrRouteDataMap }: LcrFormProps) => {
|
||||
setLocation();
|
||||
if (currentServiceProvider) {
|
||||
setApiUrl(
|
||||
`ServiceProviders/${currentServiceProvider.service_provider_sid}/VoipCarriers`
|
||||
`ServiceProviders/${currentServiceProvider.service_provider_sid}/VoipCarriers`,
|
||||
);
|
||||
}
|
||||
}, [user, currentServiceProvider, accountSid]);
|
||||
@@ -103,7 +99,7 @@ export const LcrForm = ({ lcrDataMap, lcrRouteDataMap }: LcrFormProps) => {
|
||||
? carriers.filter((carrier) =>
|
||||
accountSid
|
||||
? carrier.account_sid === accountSid
|
||||
: carrier.account_sid === null
|
||||
: carrier.account_sid === null,
|
||||
)
|
||||
: [];
|
||||
|
||||
@@ -122,7 +118,7 @@ export const LcrForm = ({ lcrDataMap, lcrRouteDataMap }: LcrFormProps) => {
|
||||
setErrorMessage(
|
||||
accountSid
|
||||
? "There are no available carriers defined for this account"
|
||||
: "There are no available carriers"
|
||||
: "There are no available carriers",
|
||||
);
|
||||
} else {
|
||||
setErrorMessage("");
|
||||
@@ -136,44 +132,53 @@ export const LcrForm = ({ lcrDataMap, lcrRouteDataMap }: LcrFormProps) => {
|
||||
setPreviousLcr(lcrDataMap.data);
|
||||
}
|
||||
|
||||
if (
|
||||
lcrRouteDataMap &&
|
||||
lcrRouteDataMap.data &&
|
||||
lcrRouteDataMap.data !== previousLcrRoutes
|
||||
) {
|
||||
setPreviousLcrRoutes(lcrRouteDataMap.data);
|
||||
// Find default carrier
|
||||
lcrRouteDataMap.data.forEach((lr) => {
|
||||
lr.lcr_carrier_set_entries?.forEach((entry) => {
|
||||
if (
|
||||
entry.lcr_carrier_set_entry_sid ===
|
||||
lcrDataMap?.data?.default_carrier_set_entry_sid
|
||||
) {
|
||||
setDefaultLcrCarrier(entry.voip_carrier_sid || defaultCarrier);
|
||||
setDefaultLcrCarrierSetEntrySid(
|
||||
entry.lcr_carrier_set_entry_sid || null
|
||||
);
|
||||
setDefaultLcrRouteSid(entry.lcr_route_sid || "");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
useMemo(() => {
|
||||
let default_lcr_route_sid = "";
|
||||
if (
|
||||
lcrRouteDataMap &&
|
||||
lcrRouteDataMap.data &&
|
||||
lcrRouteDataMap.data !== previousLcrRoutes
|
||||
) {
|
||||
setPreviousLcrRoutes(lcrRouteDataMap.data);
|
||||
// Find default carrier
|
||||
lcrRouteDataMap.data.forEach((lr) => {
|
||||
lr.lcr_carrier_set_entries?.forEach((entry) => {
|
||||
if (
|
||||
entry.lcr_carrier_set_entry_sid ===
|
||||
lcrDataMap?.data?.default_carrier_set_entry_sid
|
||||
) {
|
||||
setDefaultLcrCarrier(entry.voip_carrier_sid || defaultCarrier);
|
||||
setDefaultLcrCarrierSetEntrySid(
|
||||
entry.lcr_carrier_set_entry_sid || null,
|
||||
);
|
||||
default_lcr_route_sid = entry.lcr_route_sid || "";
|
||||
setDefaultLcrRoute(lr);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (lcrRouteDataMap && lcrRouteDataMap.data)
|
||||
setLcrRoutes(
|
||||
lcrRouteDataMap.data.filter(
|
||||
(route) => route.lcr_route_sid !== defaultLcrRouteSid
|
||||
)
|
||||
(route) => route.lcr_route_sid !== default_lcr_route_sid,
|
||||
),
|
||||
);
|
||||
}, [defaultLcrRouteSid]);
|
||||
}, [lcrRouteDataMap?.data]);
|
||||
|
||||
const addLcrRoutes = () => {
|
||||
const newLcrRoute = LCR_ROUTE_TEMPLATE;
|
||||
const ls = [
|
||||
...lcrRoutes,
|
||||
{
|
||||
...LCR_ROUTE_TEMPLATE,
|
||||
...newLcrRoute,
|
||||
priority: lcrRoutes.length,
|
||||
lcr_carrier_set_entries: newLcrRoute.lcr_carrier_set_entries?.map(
|
||||
(r) => ({
|
||||
...r,
|
||||
voip_carrier_sid: defaultCarrier || carrierSelectorOptions[0].value,
|
||||
}),
|
||||
),
|
||||
},
|
||||
];
|
||||
setLcrRoutes(ls);
|
||||
@@ -194,11 +199,48 @@ export const LcrForm = ({ lcrDataMap, lcrRouteDataMap }: LcrFormProps) => {
|
||||
const lcrPayload: Lcr = getLcrPayload();
|
||||
postLcr(lcrPayload)
|
||||
.then(({ json }) => {
|
||||
Promise.all(
|
||||
lcrRoutes.map((route, i) => handleLcrRoutePost(json.sid, route, i))
|
||||
)
|
||||
const lcrsPayload = lcrRoutes.map((l, i) => ({
|
||||
...l,
|
||||
lcr_carrier_set_entries: l.lcr_carrier_set_entries?.map((e) => ({
|
||||
...e,
|
||||
voip_carrier_sid:
|
||||
e.voip_carrier_sid ||
|
||||
defaultCarrier ||
|
||||
carrierSelectorOptions[0]?.value,
|
||||
})),
|
||||
lcr_sid: json.sid,
|
||||
priority: i,
|
||||
}));
|
||||
lcrsPayload.push({
|
||||
lcr_sid: json.sid,
|
||||
regex: ".*",
|
||||
description: "System Default Route",
|
||||
priority: 9999,
|
||||
lcr_carrier_set_entries: [
|
||||
{
|
||||
lcr_route_sid: "",
|
||||
voip_carrier_sid:
|
||||
defaultLcrCarrier || carrierSelectorOptions[0]?.value,
|
||||
priority: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
postLcrCreateRoutes(json.sid, lcrsPayload)
|
||||
.then(() => {
|
||||
handleLcrDefaultCarrierPost(json.sid);
|
||||
if (lcrDataMap) {
|
||||
toastSuccess("Least cost routing successfully updated");
|
||||
} else {
|
||||
toastSuccess("Least cost routing successfully created");
|
||||
if (user?.access === Scope.admin) {
|
||||
navigate(ROUTE_INTERNAL_LEST_COST_ROUTING);
|
||||
} else {
|
||||
navigate(
|
||||
`${ROUTE_INTERNAL_LEST_COST_ROUTING}/${json.sid}/edit`,
|
||||
);
|
||||
}
|
||||
// Update global state
|
||||
dispatch({ type: "lcr" });
|
||||
}
|
||||
})
|
||||
.catch(({ msg }) => {
|
||||
toastError(msg);
|
||||
@@ -209,209 +251,38 @@ export const LcrForm = ({ lcrDataMap, lcrRouteDataMap }: LcrFormProps) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleLcrDefaultCarrierPost = (lcr_sid: string) => {
|
||||
const defaultRoute = {
|
||||
lcr_sid: lcr_sid,
|
||||
regex: ".*",
|
||||
desciption: "System Default Route",
|
||||
priority: 9999,
|
||||
lcr_carrier_set_entries: [
|
||||
{
|
||||
lcr_route_sid: "",
|
||||
voip_carrier_sid: defaultLcrCarrier,
|
||||
priority: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
handleLcrRoutePost(lcr_sid, defaultRoute, 9999).then((lcr_route_sid) => {
|
||||
// There is small hack here to wait the data commited in bd for lcr entries
|
||||
new Promise(async (r) => setTimeout(() => r(0), 300)).then(() => {
|
||||
getLcrRoute(lcr_route_sid).then(({ json }) => {
|
||||
if (json.lcr_carrier_set_entries?.length) {
|
||||
const lcr_carrier_set_entry_sid =
|
||||
json.lcr_carrier_set_entries[0].lcr_carrier_set_entry_sid;
|
||||
putLcr(lcr_sid, {
|
||||
default_carrier_set_entry_sid: lcr_carrier_set_entry_sid,
|
||||
})
|
||||
.then(() => {
|
||||
if (lcrDataMap) {
|
||||
toastSuccess("Least cost routing successfully updated");
|
||||
} else {
|
||||
toastSuccess("Least cost routing successfully created");
|
||||
if (user?.access === Scope.admin) {
|
||||
navigate(ROUTE_INTERNAL_LEST_COST_ROUTING);
|
||||
} else {
|
||||
navigate(
|
||||
`${ROUTE_INTERNAL_LEST_COST_ROUTING}/${lcr_sid}/edit`
|
||||
);
|
||||
}
|
||||
// Update global state
|
||||
dispatch({ type: "lcr" });
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toastError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleLcrRoutePost = (
|
||||
lcr_sid: string,
|
||||
route: LcrRoute,
|
||||
priority: number
|
||||
): Promise<string> => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const lcrRoutePayload: LcrRoute = {
|
||||
lcr_sid,
|
||||
regex: route.regex,
|
||||
priority,
|
||||
};
|
||||
postLcrRoute(lcrRoutePayload)
|
||||
.then(({ json }) => {
|
||||
if (route.lcr_carrier_set_entries) {
|
||||
Promise.all(
|
||||
route.lcr_carrier_set_entries.map((entry) => {
|
||||
handleLcrCarrierSetEntryPost(json.sid, entry);
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
resolve(json.sid);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleLcrCarrierSetEntryPost = (
|
||||
lcr_route_sid: string,
|
||||
entry: LcrCarrierSetEntry
|
||||
): Promise<string> => {
|
||||
const lcrCarrierSetEntryPayload: LcrCarrierSetEntry = {
|
||||
...entry,
|
||||
voip_carrier_sid: entry.voip_carrier_sid || defaultCarrier,
|
||||
lcr_route_sid,
|
||||
};
|
||||
return new Promise<string>(async (r, e) => {
|
||||
postLcrCarrierSetEntry(lcrCarrierSetEntryPayload)
|
||||
.then(({ json }) => r(json.sid))
|
||||
.catch((error) => e(error));
|
||||
});
|
||||
};
|
||||
|
||||
const handleLcrPut = () => {
|
||||
if (lcrDataMap && lcrDataMap.data && lcrDataMap.data.lcr_sid) {
|
||||
// update LCR
|
||||
const lcrPayload: Lcr = getLcrPayload();
|
||||
putLcr(lcrDataMap.data.lcr_sid, lcrPayload).then(() => {
|
||||
Promise.all(
|
||||
lcrRoutes.map((route, i) => {
|
||||
if (
|
||||
route.lcr_route_sid &&
|
||||
!route.lcr_route_sid?.startsWith(NOT_AVAILABLE_PREFIX)
|
||||
) {
|
||||
handleLcrRoutePut(
|
||||
lcrDataMap.data?.lcr_sid || "",
|
||||
route.lcr_route_sid,
|
||||
route,
|
||||
i
|
||||
);
|
||||
} else {
|
||||
handleLcrRoutePost(lcrDataMap.data?.lcr_sid || "", route, i);
|
||||
}
|
||||
})
|
||||
)
|
||||
putLcrUpdateRoutes(lcrDataMap.data?.lcr_sid || "", [
|
||||
...lcrRoutes.map((r, i) => ({
|
||||
...r,
|
||||
priority: i,
|
||||
})),
|
||||
...(hasValue(defaultLcrRoute)
|
||||
? [
|
||||
{
|
||||
...defaultLcrRoute,
|
||||
lcr_carrier_set_entries:
|
||||
defaultLcrRoute.lcr_carrier_set_entries?.map((r) => ({
|
||||
...r,
|
||||
voip_carrier_sid:
|
||||
defaultLcrCarrier ||
|
||||
r.voip_carrier_sid ||
|
||||
carrierSelectorOptions[0].value,
|
||||
})),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
])
|
||||
.then(() => {
|
||||
if (defaultLcrCarrierSetEntrySid) {
|
||||
const defaultEntry: LcrCarrierSetEntry = {
|
||||
lcr_route_sid: defaultLcrRouteSid,
|
||||
voip_carrier_sid: defaultLcrCarrier,
|
||||
priority: 0,
|
||||
};
|
||||
|
||||
handleLcrCarrierEntryPut(
|
||||
defaultLcrRouteSid,
|
||||
defaultLcrCarrierSetEntrySid,
|
||||
defaultEntry
|
||||
).then(() => {
|
||||
toastSuccess("Least cost routing rule successfully updated");
|
||||
});
|
||||
}
|
||||
toastSuccess("Least cost routing rule successfully updated");
|
||||
})
|
||||
.catch((error) => toastError(error));
|
||||
});
|
||||
}
|
||||
|
||||
const handleLcrRoutePut = (
|
||||
lcr_sid: string,
|
||||
lcr_route_sid: string,
|
||||
route: LcrRoute,
|
||||
priority: number
|
||||
): Promise<string> => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const lcrRoutePayload: LcrRoute = {
|
||||
lcr_sid,
|
||||
regex: route.regex,
|
||||
priority,
|
||||
};
|
||||
putLcrRoutes(lcr_route_sid, lcrRoutePayload).then(() => {
|
||||
if (
|
||||
route.lcr_carrier_set_entries &&
|
||||
route.lcr_carrier_set_entries.length > 0
|
||||
) {
|
||||
Promise.all(
|
||||
route.lcr_carrier_set_entries.map((entry) => {
|
||||
if (entry.lcr_carrier_set_entry_sid) {
|
||||
return handleLcrCarrierEntryPut(
|
||||
entry.lcr_route_sid || lcr_route_sid,
|
||||
entry.lcr_carrier_set_entry_sid,
|
||||
entry
|
||||
);
|
||||
} else {
|
||||
return handleLcrCarrierSetEntryPost(lcr_route_sid, entry);
|
||||
}
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
resolve("Least cost routing rule successfully updated");
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleLcrCarrierEntryPut = (
|
||||
lcr_route_sid: string,
|
||||
lcr_carrier_set_entry_sid: string,
|
||||
entry: LcrCarrierSetEntry
|
||||
): Promise<string> => {
|
||||
const lcrCarrierSetEntryPayload: LcrCarrierSetEntry = {
|
||||
lcr_route_sid,
|
||||
workload: entry.workload,
|
||||
voip_carrier_sid: entry.voip_carrier_sid || defaultCarrier,
|
||||
priority: entry.priority,
|
||||
};
|
||||
return new Promise<string>(async (r, e) => {
|
||||
putLcrCarrierSetEntries(
|
||||
lcr_carrier_set_entry_sid,
|
||||
lcrCarrierSetEntryPayload
|
||||
)
|
||||
.then(() => r("success"))
|
||||
.catch((error) => e(error));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
@@ -430,7 +301,7 @@ export const LcrForm = ({ lcrDataMap, lcrRouteDataMap }: LcrFormProps) => {
|
||||
toastSuccess(
|
||||
<>
|
||||
Deleted least cost routing <strong>{lcrForDelete?.name}</strong>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
setLcrForDelete(null);
|
||||
if (user?.access === Scope.admin) {
|
||||
@@ -449,7 +320,12 @@ export const LcrForm = ({ lcrDataMap, lcrRouteDataMap }: LcrFormProps) => {
|
||||
return (
|
||||
<>
|
||||
<Section slim>
|
||||
<form className="form form--internal" onSubmit={handleSubmit}>
|
||||
<form
|
||||
className={`form form--internal ${
|
||||
!lcrDataMap?.data && lcrDataMap?.refetch ? "form--blur" : ""
|
||||
}`}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<fieldset>
|
||||
<MS>{MSG_REQUIRED_FIELDS}</MS>
|
||||
{errorMessage && <Message message={errorMessage} />}
|
||||
|
||||
@@ -32,7 +32,7 @@ export const Lcrs = () => {
|
||||
Scope.admin,
|
||||
`${ROUTE_INTERNAL_LEST_COST_ROUTING}/add`,
|
||||
user,
|
||||
"You do not have permissions to manage all outbound call routes"
|
||||
"You do not have permissions to manage all outbound call routes",
|
||||
);
|
||||
const [lcrs, refetch] = useApiData<Lcr[]>("Lcrs");
|
||||
const [filter, setFilter] = useState("");
|
||||
@@ -53,9 +53,9 @@ export const Lcrs = () => {
|
||||
accountSid
|
||||
? lcr.account_sid === accountSid
|
||||
: currentServiceProvider?.service_provider_sid
|
||||
? lcr.service_provider_sid ==
|
||||
currentServiceProvider.service_provider_sid
|
||||
: lcr.account_sid === null
|
||||
? lcr.service_provider_sid ==
|
||||
currentServiceProvider.service_provider_sid
|
||||
: lcr.account_sid === null,
|
||||
)
|
||||
: [];
|
||||
}, [accountSid, lcrs]);
|
||||
@@ -68,7 +68,7 @@ export const Lcrs = () => {
|
||||
toastSuccess(
|
||||
<>
|
||||
Deleted outbound call route <strong>{lcr?.name}</strong>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
setLcr(null);
|
||||
refetch();
|
||||
@@ -99,7 +99,7 @@ export const Lcrs = () => {
|
||||
multiple carriers available.
|
||||
</M>
|
||||
</section>
|
||||
<section className="filters filters--spaced">
|
||||
<section className="filters filters--multi">
|
||||
<SearchFilter placeholder="Filter lcrs" filter={[filter, setFilter]} />
|
||||
<ScopedAccess user={user} scope={Scope.admin}>
|
||||
<AccountFilter
|
||||
@@ -156,7 +156,7 @@ export const Lcrs = () => {
|
||||
<span>
|
||||
{lcr.account_sid
|
||||
? accounts?.find(
|
||||
(acct) => acct.account_sid === lcr.account_sid
|
||||
(acct) => acct.account_sid === lcr.account_sid,
|
||||
)?.name
|
||||
: currentServiceProvider?.name}
|
||||
</span>
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
}
|
||||
|
||||
.lcr-card:hover {
|
||||
box-shadow: -7px 7px 5px #d5d7db, -5px -5px 10px #ffffff;
|
||||
box-shadow:
|
||||
-7px 7px 5px #d5d7db,
|
||||
-5px -5px 10px #ffffff;
|
||||
transform: translateY(-3px) translateX(-3px);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { MSTeamsTenant } from "src/api/types";
|
||||
export const EditMsTeamsTenant = () => {
|
||||
const params = useParams();
|
||||
const [data, refetch, error] = useApiData<MSTeamsTenant>(
|
||||
`MicrosoftTeamsTenants/${params.ms_teams_tenant_sid}`
|
||||
`MicrosoftTeamsTenants/${params.ms_teams_tenant_sid}`,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -49,7 +49,7 @@ export const MsTeamsTenantForm = ({
|
||||
useRedirect<Account>(
|
||||
accounts,
|
||||
ROUTE_INTERNAL_ACCOUNTS,
|
||||
"You must create an account before you can create an Microsoft Teams Tenant."
|
||||
"You must create an account before you can create an Microsoft Teams Tenant.",
|
||||
);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
@@ -63,7 +63,7 @@ export const MsTeamsTenantForm = ({
|
||||
? msTeamsTenants.filter(
|
||||
(a) =>
|
||||
a.ms_teams_tenant_sid !==
|
||||
msTeamsTenant.data!.ms_teams_tenant_sid
|
||||
msTeamsTenant.data!.ms_teams_tenant_sid,
|
||||
)
|
||||
: msTeamsTenants;
|
||||
|
||||
@@ -120,7 +120,12 @@ export const MsTeamsTenantForm = ({
|
||||
|
||||
return (
|
||||
<Section slim>
|
||||
<form className="form form--internal" onSubmit={handleSubmit}>
|
||||
<form
|
||||
className={`form form--internal ${
|
||||
!msTeamsTenant?.data && msTeamsTenant?.refetch ? "form--blur" : ""
|
||||
}`}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<fieldset>
|
||||
<MS>{MSG_REQUIRED_FIELDS}</MS>
|
||||
</fieldset>
|
||||
@@ -151,7 +156,7 @@ export const MsTeamsTenantForm = ({
|
||||
applications={
|
||||
applications
|
||||
? applications.filter(
|
||||
(application) => application.account_sid === accountSid
|
||||
(application) => application.account_sid === accountSid,
|
||||
)
|
||||
: []
|
||||
}
|
||||
|
||||
@@ -32,10 +32,10 @@ import type { ACLGetIMessage } from "src/utils/with-access-control";
|
||||
|
||||
export const MSTeamsTenants = () => {
|
||||
const [msTeamsTenant, setMsTeamsTenant] = useState<MSTeamsTenant | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const [msTeamsTenants, refetch] = useApiData<MSTeamsTenant[]>(
|
||||
"MicrosoftTeamsTenants"
|
||||
"MicrosoftTeamsTenants",
|
||||
);
|
||||
const [accounts] = useServiceProviderData<Account[]>("Accounts");
|
||||
const [applications] = useServiceProviderData<Application[]>("Applications");
|
||||
@@ -45,14 +45,14 @@ export const MSTeamsTenants = () => {
|
||||
const msTeamsTenantsFiltered = useMemo(() => {
|
||||
return msTeamsTenants
|
||||
? msTeamsTenants.filter(
|
||||
(mst) => !accountSid || mst.account_sid === accountSid
|
||||
(mst) => !accountSid || mst.account_sid === accountSid,
|
||||
)
|
||||
: [];
|
||||
}, [accountSid, msTeamsTenants]);
|
||||
|
||||
const filteredMsTeamsTenants = useFilteredResults<MSTeamsTenant>(
|
||||
filter,
|
||||
msTeamsTenantsFiltered
|
||||
msTeamsTenantsFiltered,
|
||||
);
|
||||
|
||||
const handleDelete = () => {
|
||||
@@ -65,7 +65,7 @@ export const MSTeamsTenants = () => {
|
||||
<>
|
||||
Deleted Microsoft Teams Tenant{" "}
|
||||
<strong>{msTeamsTenant.tenant_fqdn}</strong>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -89,7 +89,7 @@ export const MSTeamsTenants = () => {
|
||||
</Link>
|
||||
)}
|
||||
</section>
|
||||
<section className="filters filters--spaced">
|
||||
<section className="filters filters--multi">
|
||||
<SearchFilter
|
||||
placeholder="Filter ms teams tenants"
|
||||
filter={[filter, setFilter]}
|
||||
@@ -131,7 +131,8 @@ export const MSTeamsTenants = () => {
|
||||
{
|
||||
accounts?.find(
|
||||
(acct) =>
|
||||
acct.account_sid === msTeamsTenant.account_sid
|
||||
acct.account_sid ===
|
||||
msTeamsTenant.account_sid,
|
||||
)?.name
|
||||
}
|
||||
</span>
|
||||
@@ -148,7 +149,7 @@ export const MSTeamsTenants = () => {
|
||||
{applications?.find(
|
||||
(app) =>
|
||||
app.application_sid ===
|
||||
msTeamsTenant.application_sid
|
||||
msTeamsTenant.application_sid,
|
||||
)?.name || "None"}
|
||||
</span>
|
||||
</div>
|
||||
@@ -217,5 +218,5 @@ const getAclIMessage: ACLGetIMessage = (currentServiceProvider) => {
|
||||
|
||||
export default withAccessControl(
|
||||
"hasMSTeamsFqdn",
|
||||
getAclIMessage
|
||||
getAclIMessage,
|
||||
)(MSTeamsTenants);
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { PhoneNumber } from "src/api/types";
|
||||
export const EditPhoneNumber = () => {
|
||||
const params = useParams();
|
||||
const [data, refetch, error] = useApiData<PhoneNumber>(
|
||||
`PhoneNumbers/${params.phone_number_sid}`
|
||||
`PhoneNumbers/${params.phone_number_sid}`,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -51,13 +51,13 @@ export const PhoneNumberForm = ({ phoneNumber }: PhoneNumberFormProps) => {
|
||||
useRedirect<Account>(
|
||||
accounts,
|
||||
ROUTE_INTERNAL_ACCOUNTS,
|
||||
"You must create an account before you can create a phone number."
|
||||
"You must create an account before you can create a phone number.",
|
||||
);
|
||||
|
||||
useRedirect<Carrier>(
|
||||
carriers,
|
||||
ROUTE_INTERNAL_CARRIERS,
|
||||
"You must create a SIP trunk before you can create a phone number."
|
||||
"You must create a SIP trunk before you can create a phone number.",
|
||||
);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
@@ -69,7 +69,7 @@ export const PhoneNumberForm = ({ phoneNumber }: PhoneNumberFormProps) => {
|
||||
const filtered =
|
||||
phoneNumber && phoneNumber.data
|
||||
? phoneNumbers.filter(
|
||||
(a) => a.phone_number_sid !== phoneNumber.data!.phone_number_sid
|
||||
(a) => a.phone_number_sid !== phoneNumber.data!.phone_number_sid,
|
||||
)
|
||||
: phoneNumbers;
|
||||
|
||||
@@ -90,7 +90,7 @@ export const PhoneNumberForm = ({ phoneNumber }: PhoneNumberFormProps) => {
|
||||
phoneNumber.refetch();
|
||||
toastSuccess("Phone number updated successfully");
|
||||
navigate(
|
||||
`${ROUTE_INTERNAL_PHONE_NUMBERS}/${phoneNumber.data?.phone_number_sid}/edit`
|
||||
`${ROUTE_INTERNAL_PHONE_NUMBERS}/${phoneNumber.data?.phone_number_sid}/edit`,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -141,7 +141,12 @@ export const PhoneNumberForm = ({ phoneNumber }: PhoneNumberFormProps) => {
|
||||
return (
|
||||
<>
|
||||
<Section slim>
|
||||
<form className="form form--internal" onSubmit={handleSubmit}>
|
||||
<form
|
||||
className={`form form--internal ${
|
||||
!phoneNumber?.data && phoneNumber?.refetch ? "form--blur" : ""
|
||||
}`}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<fieldset>
|
||||
<MS>{MSG_REQUIRED_FIELDS}</MS>
|
||||
</fieldset>
|
||||
@@ -196,7 +201,7 @@ export const PhoneNumberForm = ({ phoneNumber }: PhoneNumberFormProps) => {
|
||||
applications={
|
||||
applications
|
||||
? applications.filter(
|
||||
(application) => application.account_sid === accountSid
|
||||
(application) => application.account_sid === accountSid,
|
||||
)
|
||||
: []
|
||||
}
|
||||
|
||||
@@ -56,14 +56,14 @@ export const PhoneNumbers = () => {
|
||||
setAccountSid(getAccountFilter());
|
||||
return phoneNumbers
|
||||
? phoneNumbers.filter(
|
||||
(phn) => !accountSid || phn.account_sid === accountSid
|
||||
(phn) => !accountSid || phn.account_sid === accountSid,
|
||||
)
|
||||
: [];
|
||||
}, [accountSid, phoneNumbers]);
|
||||
|
||||
const filteredPhoneNumbers = useFilteredResults<PhoneNumber>(
|
||||
filter,
|
||||
phoneNumbersFiltered
|
||||
phoneNumbersFiltered,
|
||||
);
|
||||
|
||||
const handleMassEdit = () => {
|
||||
@@ -74,7 +74,7 @@ export const PhoneNumbers = () => {
|
||||
};
|
||||
|
||||
return putPhoneNumber(phoneNumber.phone_number_sid, payload);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.then(() => {
|
||||
refetch();
|
||||
@@ -98,7 +98,7 @@ export const PhoneNumbers = () => {
|
||||
toastSuccess(
|
||||
<>
|
||||
Deleted phone number <strong>{phoneNumber.number}</strong>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -129,7 +129,7 @@ export const PhoneNumbers = () => {
|
||||
</Link>
|
||||
)}
|
||||
</section>
|
||||
<section className="filters filters--spaced">
|
||||
<section className="filters filters--multi">
|
||||
<SearchFilter
|
||||
placeholder="Filter phone numbers"
|
||||
filter={[filter, setFilter]}
|
||||
@@ -177,7 +177,7 @@ export const PhoneNumbers = () => {
|
||||
application={[applicationSid, setApplicationSid]}
|
||||
applications={applications?.filter(
|
||||
(application) =>
|
||||
application.account_sid === accountSid
|
||||
application.account_sid === accountSid,
|
||||
)}
|
||||
defaultOption="None"
|
||||
/>
|
||||
@@ -224,7 +224,7 @@ export const PhoneNumbers = () => {
|
||||
selectedPhoneNumbers.find(
|
||||
(phone) =>
|
||||
phone.phone_number_sid ===
|
||||
phoneNumber.phone_number_sid
|
||||
phoneNumber.phone_number_sid,
|
||||
)
|
||||
? true
|
||||
: false
|
||||
@@ -240,8 +240,8 @@ export const PhoneNumbers = () => {
|
||||
curr.filter(
|
||||
(phone) =>
|
||||
phone.phone_number_sid !==
|
||||
phoneNumber.phone_number_sid
|
||||
)
|
||||
phoneNumber.phone_number_sid,
|
||||
),
|
||||
);
|
||||
}
|
||||
}}
|
||||
@@ -270,7 +270,8 @@ export const PhoneNumbers = () => {
|
||||
{
|
||||
accounts?.find(
|
||||
(acct) =>
|
||||
acct.account_sid === phoneNumber.account_sid
|
||||
acct.account_sid ===
|
||||
phoneNumber.account_sid,
|
||||
)?.name
|
||||
}
|
||||
</span>
|
||||
@@ -287,7 +288,7 @@ export const PhoneNumbers = () => {
|
||||
{applications?.find(
|
||||
(app) =>
|
||||
app.application_sid ===
|
||||
phoneNumber.application_sid
|
||||
phoneNumber.application_sid,
|
||||
)?.name || "None"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -98,7 +98,7 @@ export const CallTracing = ({ call }: CallTracingProps) => {
|
||||
rootGroup.children = buildChildren(
|
||||
rootGroup.level + 1,
|
||||
rootGroup,
|
||||
groups
|
||||
groups,
|
||||
);
|
||||
setJaegerGroup(rootGroup);
|
||||
}
|
||||
@@ -108,7 +108,7 @@ export const CallTracing = ({ call }: CallTracingProps) => {
|
||||
const buildChildren = (
|
||||
level: number,
|
||||
rootGroup: JaegerGroup,
|
||||
groups: JaegerGroup[]
|
||||
groups: JaegerGroup[],
|
||||
): JaegerGroup[] => {
|
||||
return getGroupsByParent(rootGroup.spanId, groups).map((group) => {
|
||||
group.level = level;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { JaegerGroup, JaegerValue } from "src/api/jaeger-types";
|
||||
import dayjs from "dayjs";
|
||||
import "./styles.scss";
|
||||
import { formattedDuration } from "./utils";
|
||||
import { getSpansByNameRegex } from "../utils";
|
||||
|
||||
type JaegerDetailProps = {
|
||||
group: JaegerGroup;
|
||||
@@ -65,6 +66,37 @@ export const JaegerDetail = ({ group }: JaegerDetailProps) => {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* TTS Streaming Attrs */}
|
||||
{group.children.length &&
|
||||
getSpansByNameRegex(group.children, /tts-generation/)?.map((span) => {
|
||||
return span.attributes.map((attribute) => {
|
||||
if (
|
||||
![
|
||||
"tts.vendor",
|
||||
"tts.language",
|
||||
"tts.voice",
|
||||
"tts.cached",
|
||||
"engine",
|
||||
"voice",
|
||||
].includes(attribute.key)
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
key={attribute.key}
|
||||
className="spanDetailsWrapper__details"
|
||||
>
|
||||
<div className="spanDetailsWrapper__details_header">
|
||||
<strong>{attribute.key}</strong>:
|
||||
</div>
|
||||
<div className="spanDetailsWrapper__details_body">
|
||||
{extractSpanGroupValue(attribute.value)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -13,11 +13,12 @@ import {
|
||||
JaegerRoot,
|
||||
JaegerSpan,
|
||||
WaveSurferDtmfResult,
|
||||
WaveSurferGatherSpeechVerbHookLatencyResult,
|
||||
WaveSurferSttResult,
|
||||
WaveSurferTtsLatencyResult,
|
||||
} from "src/api/jaeger-types";
|
||||
import {
|
||||
getSpanAttributeByName,
|
||||
getSpansByName,
|
||||
getSpansByNameRegex,
|
||||
getSpansFromJaegerRoot,
|
||||
} from "./utils";
|
||||
@@ -42,6 +43,14 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
useState<WaveSurferSttResult | null>();
|
||||
const [waveSurferDtmfData, setWaveSurferDtmfData] =
|
||||
useState<WaveSurferDtmfResult | null>();
|
||||
|
||||
const [waveSurferTtsLatencyData, setWaveSurferTtsLatencyData] =
|
||||
useState<WaveSurferTtsLatencyResult | null>();
|
||||
|
||||
const [
|
||||
waveSurferGatherSpeechVerbHookLatencyData,
|
||||
setWaveSurferGatherSpeechVerbHookLatencyData,
|
||||
] = useState<WaveSurferGatherSpeechVerbHookLatencyResult | null>();
|
||||
const [regionChecked, setRegionChecked] = useState(false);
|
||||
|
||||
const wavesurferId = `wavesurfer--${call_sid}`;
|
||||
@@ -62,7 +71,7 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
const [dtmfValue] = getSpanAttributeByName(s.attributes, "dtmf");
|
||||
const [durationValue] = getSpanAttributeByName(
|
||||
s.attributes,
|
||||
"duration"
|
||||
"duration",
|
||||
);
|
||||
if (dtmfValue && durationValue) {
|
||||
const start =
|
||||
@@ -112,10 +121,47 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
});
|
||||
};
|
||||
|
||||
const PEAKS_WINDOW = 5; // require 30 ms of speech energy over threshold to trigger
|
||||
const PEAK_THRESHOLD = 0.03;
|
||||
|
||||
const getSilenceStartTime = (
|
||||
start: number,
|
||||
end: number,
|
||||
channel: number,
|
||||
): number => {
|
||||
if (waveSurferRef.current) {
|
||||
const duration = waveSurferRef.current.getDecodedData()?.duration;
|
||||
if (duration && duration > 0) {
|
||||
const maxLength = Math.round(duration * 8000) / 10; // evaluate speech energy every 10 ms
|
||||
const peaks = waveSurferRef.current.exportPeaks({ maxLength });
|
||||
if (peaks && peaks.length > channel) {
|
||||
if (duration && duration > 0) {
|
||||
const data = peaks[channel];
|
||||
const startPeak = Math.ceil((start * data.length) / duration);
|
||||
const endPeak = Math.ceil((end * data.length) / duration);
|
||||
let count = 0;
|
||||
for (let i = endPeak; i > startPeak; i--)
|
||||
if (Math.abs(data[i]) > PEAK_THRESHOLD) {
|
||||
count++;
|
||||
if (count === PEAKS_WINDOW) {
|
||||
return (
|
||||
((i + PEAKS_WINDOW) * duration) / data.length + 0.02 // add 20 ms adjustment
|
||||
);
|
||||
}
|
||||
} else {
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
const drawSttRegionForSpan = (
|
||||
s: JaegerSpan,
|
||||
startPoint: JaegerSpan,
|
||||
channel = 0
|
||||
channel = 0,
|
||||
) => {
|
||||
if (waveSurferRegionsPluginRef.current) {
|
||||
const r = waveSurferRegionsPluginRef.current
|
||||
@@ -128,30 +174,46 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
const end =
|
||||
(s.endTimeUnixNano - startPoint.startTimeUnixNano) / 1_000_000_000;
|
||||
|
||||
const region = waveSurferRegionsPluginRef.current.addRegion({
|
||||
id: s.spanId,
|
||||
start,
|
||||
end,
|
||||
color: "rgba(255, 0, 0, 0.15)",
|
||||
drag: false,
|
||||
resize: false,
|
||||
});
|
||||
const endSpeechTime = getSilenceStartTime(start, end, channel);
|
||||
|
||||
changeRegionMouseStyle(region, channel);
|
||||
const [sttResult] = getSpanAttributeByName(s.attributes, "stt.result");
|
||||
let att: WaveSurferSttResult;
|
||||
if (sttResult) {
|
||||
const data = JSON.parse(sttResult.value.stringValue);
|
||||
|
||||
att = {
|
||||
vendor: data.vendor.name,
|
||||
transcript: data.alternatives[0].transcript,
|
||||
confidence: data.alternatives[0].confidence,
|
||||
language_code: data.language_code,
|
||||
...(endSpeechTime > 0 && { latency: end - endSpeechTime }),
|
||||
};
|
||||
|
||||
const [sttResolve] = getSpanAttributeByName(
|
||||
s.attributes,
|
||||
"stt.resolve",
|
||||
);
|
||||
if (
|
||||
endSpeechTime > 0 &&
|
||||
sttResolve &&
|
||||
sttResolve.value.stringValue === "speech"
|
||||
) {
|
||||
const latencyRegion = waveSurferRegionsPluginRef.current.addRegion({
|
||||
id: s.spanId + "latency",
|
||||
start: endSpeechTime,
|
||||
end,
|
||||
color: "rgba(255, 255, 0, 0.55)",
|
||||
drag: false,
|
||||
resize: false,
|
||||
content: `${(end - endSpeechTime).toFixed(2)}s`,
|
||||
});
|
||||
|
||||
changeRegionMouseStyle(latencyRegion, channel);
|
||||
}
|
||||
} else {
|
||||
const [sttResolve] = getSpanAttributeByName(
|
||||
s.attributes,
|
||||
"stt.resolve"
|
||||
"stt.resolve",
|
||||
);
|
||||
if (sttResolve && sttResolve.value.stringValue === "timeout") {
|
||||
att = {
|
||||
@@ -171,6 +233,17 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
const region = waveSurferRegionsPluginRef.current.addRegion({
|
||||
id: s.spanId,
|
||||
start,
|
||||
end,
|
||||
color: "rgba(255, 0, 0, 0.15)",
|
||||
drag: false,
|
||||
resize: false,
|
||||
});
|
||||
|
||||
changeRegionMouseStyle(region, channel);
|
||||
|
||||
region.on("click", () => {
|
||||
setWaveSurferRegionData(att);
|
||||
});
|
||||
@@ -178,10 +251,103 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const drawTtsLatencyRegion = (s: JaegerSpan, startPoint: JaegerSpan) => {
|
||||
if (waveSurferRegionsPluginRef.current) {
|
||||
const r = waveSurferRegionsPluginRef.current
|
||||
.getRegions()
|
||||
.find((r) => r.id === s.spanId);
|
||||
if (!r) {
|
||||
const start =
|
||||
(s.startTimeUnixNano - startPoint.startTimeUnixNano) / 1_000_000_000;
|
||||
let end =
|
||||
(s.endTimeUnixNano - startPoint.startTimeUnixNano) / 1_000_000_000;
|
||||
|
||||
const [ttsVendor] = getSpanAttributeByName(s.attributes, "tts.vendor");
|
||||
const [ttsCache] = getSpanAttributeByName(s.attributes, "tts.cached");
|
||||
const [streamLatency] = getSpanAttributeByName(
|
||||
s.attributes,
|
||||
"time_to_first_byte_ms",
|
||||
);
|
||||
if (streamLatency && streamLatency.value.stringValue) {
|
||||
end = start + Number(streamLatency.value.stringValue) / 1_000;
|
||||
}
|
||||
if (ttsVendor && ttsCache && !Boolean(ttsCache.value.boolValue)) {
|
||||
const latencyRegion = waveSurferRegionsPluginRef.current.addRegion({
|
||||
id: s.spanId,
|
||||
start: start,
|
||||
end,
|
||||
color: "rgba(255, 155, 0, 0.55)",
|
||||
drag: false,
|
||||
resize: false,
|
||||
content: createMultiLineTextElement(`${(end - start).toFixed(2)}s`),
|
||||
});
|
||||
|
||||
changeRegionMouseStyle(latencyRegion, 1);
|
||||
|
||||
latencyRegion.on("click", () => {
|
||||
setWaveSurferTtsLatencyData({
|
||||
vendor: ttsVendor.value.stringValue,
|
||||
latency: `${(end - start).toFixed(2)}s`,
|
||||
isCached: String(ttsCache.value.boolValue),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const drawVerbHookDelayRegion = (s: JaegerSpan, startPoint: JaegerSpan) => {
|
||||
if (waveSurferRegionsPluginRef.current) {
|
||||
const r = waveSurferRegionsPluginRef.current
|
||||
.getRegions()
|
||||
.find((r) => r.id === s.spanId);
|
||||
|
||||
if (!r) {
|
||||
const start =
|
||||
(s.startTimeUnixNano - startPoint.startTimeUnixNano) / 1_000_000_000;
|
||||
const end =
|
||||
(s.endTimeUnixNano - startPoint.startTimeUnixNano) / 1_000_000_000;
|
||||
const tmpEnd = end - start < 0.05 ? start + 0.05 : end;
|
||||
|
||||
const latencyRegion = waveSurferRegionsPluginRef.current.addRegion({
|
||||
id: s.spanId,
|
||||
start: start,
|
||||
end: tmpEnd,
|
||||
color: "rgba(255, 3, 180, 0.55)",
|
||||
drag: false,
|
||||
resize: false,
|
||||
content: createMultiLineTextElement(`${(end - start).toFixed(2)}s`),
|
||||
});
|
||||
const [statusCode] = getSpanAttributeByName(
|
||||
s.attributes,
|
||||
"http.statusCode",
|
||||
);
|
||||
changeRegionMouseStyle(latencyRegion, 0);
|
||||
latencyRegion.on("click", () => {
|
||||
setWaveSurferGatherSpeechVerbHookLatencyData({
|
||||
statusCode: statusCode ? Number(statusCode.value.doubleValue) : 404,
|
||||
latency: `${(end - start).toFixed(2)}s`,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function createMultiLineTextElement(text: string) {
|
||||
const div = document.createElement("div");
|
||||
div.style.paddingLeft = "10px";
|
||||
div.style.paddingTop = "15px";
|
||||
div.appendChild(document.createElement("br"));
|
||||
div.appendChild(document.createTextNode(text));
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
const buildWavesurferRegion = () => {
|
||||
if (jaegerRoot) {
|
||||
const spans = getSpansFromJaegerRoot(jaegerRoot);
|
||||
const [startPoint] = getSpansByName(spans, "background-listen:listen");
|
||||
const start = getSpansByNameRegex(spans, /background-record:listen/);
|
||||
const startPoint = start ? start[0] : null;
|
||||
// there should be only one startPoint for background listen
|
||||
if (startPoint) {
|
||||
const gatherSpans = getSpansByNameRegex(spans, /:gather{/);
|
||||
@@ -189,6 +355,7 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
drawSttRegionForSpan(s, startPoint);
|
||||
});
|
||||
|
||||
// Trasscription
|
||||
const transcribeSpans = getSpansByNameRegex(spans, /stt-listen:/);
|
||||
transcribeSpans.forEach((cs) => {
|
||||
// Channel start from 0
|
||||
@@ -196,14 +363,38 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
drawSttRegionForSpan(
|
||||
cs,
|
||||
startPoint,
|
||||
channel > 0 ? channel - 1 : channel
|
||||
channel > 0 ? channel - 1 : channel,
|
||||
);
|
||||
});
|
||||
|
||||
// DTMF
|
||||
const dtmfSpans = getSpansByNameRegex(spans, /dtmf:/);
|
||||
dtmfSpans.forEach((ds) => {
|
||||
drawDtmfRegionForSpan(ds, startPoint);
|
||||
});
|
||||
// TTS delay
|
||||
const ttsSpans = getSpansByNameRegex(spans, /tts-generation/);
|
||||
ttsSpans.forEach((tts) => {
|
||||
drawTtsLatencyRegion(tts, startPoint);
|
||||
});
|
||||
|
||||
// Gather verb hook delay
|
||||
const verbHookSpans = getSpansByNameRegex(spans, /verb:hook/);
|
||||
verbHookSpans
|
||||
.filter((s) => {
|
||||
const [httpBody] = getSpanAttributeByName(
|
||||
s.attributes,
|
||||
"http.body",
|
||||
);
|
||||
return (
|
||||
httpBody.value.stringValue.includes(
|
||||
'"reason":"speechDetected"',
|
||||
) ||
|
||||
httpBody.value.stringValue.includes('"reason":"dtmfDetected"')
|
||||
);
|
||||
})
|
||||
.forEach((s) => {
|
||||
drawVerbHookDelayRegion(s, startPoint);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -324,8 +515,8 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
idx <= 0
|
||||
? 0
|
||||
: idx >= waveSurferRef.current.getDuration()
|
||||
? waveSurferRef.current.getDuration() - 1
|
||||
: idx;
|
||||
? waveSurferRef.current.getDuration() - 1
|
||||
: idx;
|
||||
waveSurferRef.current.setTime(value);
|
||||
setPlayBackTime(formatTime(value));
|
||||
}
|
||||
@@ -423,7 +614,7 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div>Overlay STT and DTMF events</div>
|
||||
<div>Show latencies</div>
|
||||
</label>
|
||||
</div>
|
||||
{waveSurferRegionData && (
|
||||
@@ -475,6 +666,16 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{waveSurferRegionData.latency && (
|
||||
<div className="spanDetailsWrapper__details">
|
||||
<div className="spanDetailsWrapper__details_header">
|
||||
<strong>Latency:</strong>
|
||||
</div>
|
||||
<div className="spanDetailsWrapper__details_body">
|
||||
{waveSurferRegionData.latency.toFixed(2)} seconds
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ModalClose>
|
||||
@@ -509,6 +710,77 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
</div>
|
||||
</ModalClose>
|
||||
)}
|
||||
{waveSurferTtsLatencyData && (
|
||||
<ModalClose handleClose={() => setWaveSurferTtsLatencyData(null)}>
|
||||
<div className="spanDetailsWrapper__header">
|
||||
<P>
|
||||
<strong>Tts Latency</strong>
|
||||
</P>
|
||||
</div>
|
||||
<div className="spanDetailsWrapper">
|
||||
<div className="spanDetailsWrapper__detailsWrapper">
|
||||
<div className="spanDetailsWrapper__details">
|
||||
<div className="spanDetailsWrapper__details_header">
|
||||
<strong>Vendor:</strong>
|
||||
</div>
|
||||
<div className="spanDetailsWrapper__details_body">
|
||||
{waveSurferTtsLatencyData.vendor}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="spanDetailsWrapper__details">
|
||||
<div className="spanDetailsWrapper__details_header">
|
||||
<strong>Latency:</strong>
|
||||
</div>
|
||||
<div className="spanDetailsWrapper__details_body">
|
||||
{waveSurferTtsLatencyData.latency}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="spanDetailsWrapper__details">
|
||||
<div className="spanDetailsWrapper__details_header">
|
||||
<strong>From Cache:</strong>
|
||||
</div>
|
||||
<div className="spanDetailsWrapper__details_body">
|
||||
{waveSurferTtsLatencyData.isCached}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalClose>
|
||||
)}
|
||||
{waveSurferGatherSpeechVerbHookLatencyData && (
|
||||
<ModalClose
|
||||
handleClose={() => setWaveSurferGatherSpeechVerbHookLatencyData(null)}
|
||||
>
|
||||
<div className="spanDetailsWrapper__header">
|
||||
<P>
|
||||
<strong>Application Response Latency</strong>
|
||||
</P>
|
||||
</div>
|
||||
<div className="spanDetailsWrapper">
|
||||
<div className="spanDetailsWrapper__detailsWrapper">
|
||||
<div className="spanDetailsWrapper__details">
|
||||
<div className="spanDetailsWrapper__details_header">
|
||||
<strong>Status Code:</strong>
|
||||
</div>
|
||||
<div className="spanDetailsWrapper__details_body">
|
||||
{waveSurferGatherSpeechVerbHookLatencyData.statusCode}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="spanDetailsWrapper__details">
|
||||
<div className="spanDetailsWrapper__details_header">
|
||||
<strong>Latency:</strong>
|
||||
</div>
|
||||
<div className="spanDetailsWrapper__details_body">
|
||||
{waveSurferGatherSpeechVerbHookLatencyData.latency}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalClose>
|
||||
)}
|
||||
{deleteRecordUrl && (
|
||||
<Modal
|
||||
handleCancel={() => setDeleteRecordUrl("")}
|
||||
|
||||
@@ -11,12 +11,12 @@ export const getSpansFromJaegerRoot = (trace: JaegerRoot) => {
|
||||
!(
|
||||
attr.key.startsWith("telemetry") ||
|
||||
attr.key.startsWith("internal")
|
||||
)
|
||||
),
|
||||
);
|
||||
value.attributes = attrs;
|
||||
spans.push(value);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
spans.sort((a, b) => a.startTimeUnixNano - b.startTimeUnixNano);
|
||||
@@ -25,14 +25,14 @@ export const getSpansFromJaegerRoot = (trace: JaegerRoot) => {
|
||||
|
||||
export const getSpansByName = (
|
||||
spans: JaegerSpan[],
|
||||
name: string
|
||||
name: string,
|
||||
): JaegerSpan[] => {
|
||||
return spans.filter((s) => s.name === name);
|
||||
};
|
||||
|
||||
export const getSpansByNameRegex = (
|
||||
spans: JaegerSpan[],
|
||||
pattern: RegExp
|
||||
pattern: RegExp,
|
||||
): JaegerSpan[] => {
|
||||
const matcher = new RegExp(pattern);
|
||||
return spans.filter((s) => matcher.test(s.name));
|
||||
@@ -40,7 +40,7 @@ export const getSpansByNameRegex = (
|
||||
|
||||
export const getSpanAttributeByName = (
|
||||
attr: JaegerAttribute[],
|
||||
name: string
|
||||
name: string,
|
||||
): JaegerAttribute[] => {
|
||||
return attr.filter((a) => a.key === name);
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ export const AdminSettings = () => {
|
||||
if (hasValue(passwordSettings)) {
|
||||
setRequireDigit(passwordSettings.require_digit > 0 ? true : false);
|
||||
setRequireSpecialCharacter(
|
||||
passwordSettings.require_special_character > 0 ? true : false
|
||||
passwordSettings.require_special_character > 0 ? true : false,
|
||||
);
|
||||
if (passwordSettings.min_password_length) {
|
||||
setMinPasswordLength(passwordSettings.min_password_length);
|
||||
|
||||
@@ -27,7 +27,7 @@ export const Settings = ({ currentServiceProvider }: SettingsProps) => {
|
||||
Scope.service_provider,
|
||||
`${ROUTE_INTERNAL_ACCOUNTS}/${user?.account_sid}/edit`,
|
||||
user,
|
||||
"You do not have permissions to manage Settings"
|
||||
"You do not have permissions to manage Settings",
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -56,13 +56,13 @@ export const ServiceProviderSettings = ({
|
||||
return limit.quantity === ""
|
||||
? deleteServiceProviderLimit(
|
||||
currentServiceProvider.service_provider_sid,
|
||||
limit.category
|
||||
limit.category,
|
||||
)
|
||||
: postServiceProviderLimit(
|
||||
currentServiceProvider.service_provider_sid,
|
||||
limit
|
||||
limit,
|
||||
);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.then(() => {
|
||||
refetchLimits();
|
||||
@@ -93,7 +93,7 @@ export const ServiceProviderSettings = ({
|
||||
<>
|
||||
Deleted service provider{" "}
|
||||
<strong>{currentServiceProvider.name}</strong>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
removeActiveSP();
|
||||
})
|
||||
@@ -193,5 +193,5 @@ export const ServiceProviderSettings = ({
|
||||
};
|
||||
|
||||
export default withSelectState(["serviceProviders", "currentServiceProvider"])(
|
||||
ServiceProviderSettings
|
||||
ServiceProviderSettings,
|
||||
);
|
||||
|
||||
@@ -24,17 +24,17 @@ export const EditSpeechService = () => {
|
||||
ROUTE_INTERNAL_SPEECH,
|
||||
user,
|
||||
"You do not have access to this resource",
|
||||
data
|
||||
data,
|
||||
);
|
||||
|
||||
const getUrlForSpeech = () => {
|
||||
if (user && user?.scope === USER_ACCOUNT) {
|
||||
setUrl(
|
||||
`Accounts/${user?.account_sid}/SpeechCredentials/${params.speech_credential_sid}`
|
||||
`Accounts/${user?.account_sid}/SpeechCredentials/${params.speech_credential_sid}`,
|
||||
);
|
||||
} else {
|
||||
setUrl(
|
||||
`ServiceProviders/${currentServiceProvider?.service_provider_sid}/SpeechCredentials/${params.speech_credential_sid}`
|
||||
`ServiceProviders/${currentServiceProvider?.service_provider_sid}/SpeechCredentials/${params.speech_credential_sid}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -49,27 +49,27 @@ export const SpeechServices = () => {
|
||||
? credentials.filter((credential) =>
|
||||
accountSid
|
||||
? credential.account_sid === accountSid
|
||||
: credential.account_sid === null
|
||||
: credential.account_sid === null,
|
||||
)
|
||||
: [];
|
||||
}, [accountSid, accounts, credentials]);
|
||||
|
||||
const filteredCredentials = useFilteredResults<SpeechCredential>(
|
||||
filter,
|
||||
credentialsFiltered
|
||||
credentialsFiltered,
|
||||
);
|
||||
|
||||
const handleDelete = () => {
|
||||
if (credential && currentServiceProvider) {
|
||||
if (isUserAccountScope(accountSid, user)) {
|
||||
toastError(
|
||||
"You do not have permissions to delete these Speech Credentials"
|
||||
"You do not have permissions to delete these Speech Credentials",
|
||||
);
|
||||
return;
|
||||
}
|
||||
deleteSpeechService(
|
||||
currentServiceProvider.service_provider_sid,
|
||||
credential.speech_credential_sid
|
||||
credential.speech_credential_sid,
|
||||
)
|
||||
.then(() => {
|
||||
setCredential(null);
|
||||
@@ -81,7 +81,7 @@ export const SpeechServices = () => {
|
||||
{credential.vendor}
|
||||
{credential.label ? ` (${credential.label})` : ""}
|
||||
</strong>{" "}
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -97,7 +97,7 @@ export const SpeechServices = () => {
|
||||
setApiUrl(`Accounts/${accountSid}/SpeechCredentials`);
|
||||
} else {
|
||||
setApiUrl(
|
||||
`ServiceProviders/${currentServiceProvider?.service_provider_sid}/SpeechCredentials`
|
||||
`ServiceProviders/${currentServiceProvider?.service_provider_sid}/SpeechCredentials`,
|
||||
);
|
||||
}
|
||||
}, [currentServiceProvider, accountSid]);
|
||||
@@ -112,7 +112,7 @@ export const SpeechServices = () => {
|
||||
</Icon>
|
||||
</Link>
|
||||
</section>
|
||||
<section className="filters filters--ender">
|
||||
<section className="filters filters--multi">
|
||||
<ScopedAccess user={user} scope={Scope.service_provider}>
|
||||
<AccountFilter
|
||||
account={[accountSid, setAccountSid]}
|
||||
@@ -149,7 +149,7 @@ export const SpeechServices = () => {
|
||||
Vendor:{" "}
|
||||
{credential.vendor.startsWith(VENDOR_CUSTOM)
|
||||
? credential.vendor.substring(
|
||||
VENDOR_CUSTOM.length + 1
|
||||
VENDOR_CUSTOM.length + 1,
|
||||
)
|
||||
: credential.vendor}
|
||||
</strong>
|
||||
|
||||
@@ -37,8 +37,8 @@ export const CredentialStatus = ({
|
||||
status === CRED_OK
|
||||
? "teal"
|
||||
: status === CRED_NOT_TESTED
|
||||
? "jean"
|
||||
: "jam"
|
||||
? "jean"
|
||||
: "jam"
|
||||
}`}
|
||||
title={status === CRED_NOT_TESTED ? notTestedTxt : reason}
|
||||
>
|
||||
@@ -61,11 +61,11 @@ export const CredentialStatus = ({
|
||||
useEffect(() => {
|
||||
if (user && user.scope === USER_ACCOUNT) {
|
||||
setApiUrl(
|
||||
`Accounts/${user.account_sid}/SpeechCredentials/${cred.speech_credential_sid}/test`
|
||||
`Accounts/${user.account_sid}/SpeechCredentials/${cred.speech_credential_sid}/test`,
|
||||
);
|
||||
} else if (currentServiceProvider) {
|
||||
setApiUrl(
|
||||
`ServiceProviders/${currentServiceProvider.service_provider_sid}/SpeechCredentials/${cred.speech_credential_sid}/test`
|
||||
`ServiceProviders/${currentServiceProvider.service_provider_sid}/SpeechCredentials/${cred.speech_credential_sid}/test`,
|
||||
);
|
||||
}
|
||||
}, [user, cred, currentServiceProvider]);
|
||||
|
||||
@@ -14,7 +14,7 @@ export const getObscuredGoogleServiceKey = (key: GoogleServiceKey) => {
|
||||
return {
|
||||
...key,
|
||||
private_key: `${keyHeader}${getObscuredSecret(
|
||||
key.private_key.slice(keyHeader.length, key.private_key.length)
|
||||
key.private_key.slice(keyHeader.length, key.private_key.length),
|
||||
)}`,
|
||||
};
|
||||
};
|
||||
@@ -23,15 +23,15 @@ export const getUsage = (cred: SpeechCredential) => {
|
||||
return cred.use_for_tts && cred.use_for_stt
|
||||
? "TTS/STT"
|
||||
: cred.use_for_tts
|
||||
? "TTS"
|
||||
: cred.use_for_stt
|
||||
? "STT"
|
||||
: "Not in use";
|
||||
? "TTS"
|
||||
: cred.use_for_stt
|
||||
? "STT"
|
||||
: "Not in use";
|
||||
};
|
||||
|
||||
export const getStatus = (
|
||||
cred: SpeechCredential,
|
||||
testResult: CredentialTestResult
|
||||
testResult: CredentialTestResult,
|
||||
): CredentialStatus => {
|
||||
if (
|
||||
(cred.use_for_tts &&
|
||||
@@ -60,7 +60,7 @@ export const getStatus = (
|
||||
|
||||
export const getReason = (
|
||||
cred: SpeechCredential,
|
||||
testResult: CredentialTestResult
|
||||
testResult: CredentialTestResult,
|
||||
) => {
|
||||
const ok = "Connection test successful";
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ export const UserForm = ({ user }: UserFormProps) => {
|
||||
const [email, setEmail] = useState("");
|
||||
const [initialPassword, setInitialPassword] = useState("");
|
||||
const [scope, setScope] = useState<UserScopes | null>(
|
||||
currentUser?.scope || null
|
||||
currentUser?.scope || null,
|
||||
);
|
||||
const [isActive, setIsActive] = useState(true);
|
||||
const [forceChange, setForceChange] = useState(true);
|
||||
@@ -80,7 +80,7 @@ export const UserForm = ({ user }: UserFormProps) => {
|
||||
toastSuccess(
|
||||
<>
|
||||
Deleted user <strong>{user?.data?.name}</strong>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
handleSelfDetete();
|
||||
})
|
||||
@@ -183,7 +183,12 @@ export const UserForm = ({ user }: UserFormProps) => {
|
||||
return (
|
||||
<>
|
||||
<Section slim>
|
||||
<form className="form form--internal" onSubmit={handleSubmit}>
|
||||
<form
|
||||
className={`form form--internal ${
|
||||
!user?.data && user?.refetch ? "form--blur" : ""
|
||||
}`}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<fieldset>
|
||||
<MS>{MSG_REQUIRED_FIELDS}</MS>
|
||||
</fieldset>
|
||||
@@ -197,7 +202,8 @@ export const UserForm = ({ user }: UserFormProps) => {
|
||||
options={
|
||||
currentUser?.scope === USER_SP
|
||||
? USER_SCOPE_SELECTION.filter(
|
||||
(opt) => opt.value !== USER_ADMIN && opt.value !== "all"
|
||||
(opt) =>
|
||||
opt.value !== USER_ADMIN && opt.value !== "all",
|
||||
)
|
||||
: USER_SCOPE_SELECTION.filter((e) => e.value !== "all")
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export const Users = () => {
|
||||
|
||||
if (scopeFilter !== "all" && accountSid) {
|
||||
return serviceProviderUsers?.filter(
|
||||
(e) => e.scope === scopeFilter && accountSid === e.account_sid
|
||||
(e) => e.scope === scopeFilter && accountSid === e.account_sid,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export const Users = () => {
|
||||
}, [accountSid, scopeFilter, users, accounts, currentServiceProvider]);
|
||||
|
||||
const filteredUsers = useFilteredResults<User>(filter, usersFiltered)?.sort(
|
||||
sortUsersAlpha
|
||||
sortUsersAlpha,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -88,7 +88,7 @@ export const Users = () => {
|
||||
</Icon>
|
||||
</Link>
|
||||
</section>
|
||||
<section className="filters filters--mix">
|
||||
<section className="filters filters--multi">
|
||||
<section>
|
||||
<SearchFilter
|
||||
placeholder="Filter users"
|
||||
@@ -137,8 +137,8 @@ export const Users = () => {
|
||||
{user.scope === USER_ADMIN
|
||||
? "All"
|
||||
: user.account_name
|
||||
? `Account: ${user.account_name}`
|
||||
: `Service Provider: ${user.service_provider_name}`}
|
||||
? `Account: ${user.account_name}`
|
||||
: `Service Provider: ${user.service_provider_name}`}
|
||||
</div>
|
||||
<div className="item__actions">
|
||||
<Link
|
||||
|
||||
@@ -63,7 +63,7 @@ export const CreatePassword = () => {
|
||||
<li>Contain at least one special character</li>
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -90,7 +90,7 @@ export const CreatePassword = () => {
|
||||
navigate(
|
||||
userData.scope !== USER_ACCOUNT
|
||||
? ROUTE_INTERNAL_ACCOUNTS
|
||||
: ROUTE_INTERNAL_APPLICATIONS
|
||||
: ROUTE_INTERNAL_APPLICATIONS,
|
||||
);
|
||||
} else {
|
||||
setMessage(MSG_SOMETHING_WRONG);
|
||||
|
||||
@@ -24,7 +24,7 @@ export const ForgotPassword = () => {
|
||||
.then((response) => {
|
||||
if (response.status === StatusCodes.NO_CONTENT) {
|
||||
toastSuccess(
|
||||
"A password reset email has been sent to your email. Please check your inbox (and, possibly, spam folder) and follow the instructions to reset your password."
|
||||
"A password reset email has been sent to your email. Please check your inbox (and, possibly, spam folder) and follow the instructions to reset your password.",
|
||||
);
|
||||
navigate(ROUTE_LOGIN);
|
||||
} else {
|
||||
|
||||
@@ -20,7 +20,7 @@ export const ResetPassword = () => {
|
||||
setMessage("");
|
||||
if (newPassword !== confirmNewPassword) {
|
||||
setMessage(
|
||||
"The confirmation password does not match the new password. Please ensure both passwords are identical."
|
||||
"The confirmation password does not match the new password. Please ensure both passwords are identical.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ export const RegisterChooseSubdomain = () => {
|
||||
getAvailability(`${name}.${rootDomain}`)
|
||||
.then(({ json }) =>
|
||||
setIsValidDomain(
|
||||
Boolean(json.available) && hasValue(name) && name.length != 0
|
||||
)
|
||||
Boolean(json.available) && hasValue(name) && name.length != 0,
|
||||
),
|
||||
)
|
||||
.catch((error) => {
|
||||
setErrorMessage(error.msg);
|
||||
|
||||
@@ -19,5 +19,5 @@ createRoot(root).render(
|
||||
</AuthProvider>
|
||||
</BrowserRouter>
|
||||
</StateProvider>
|
||||
</React.StrictMode>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
@@ -84,7 +84,7 @@ export const parseJwt = (token: string) => {
|
||||
.map((c) => {
|
||||
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
})
|
||||
.join("")
|
||||
.join(""),
|
||||
);
|
||||
|
||||
return JSON.parse(jsonPayload);
|
||||
@@ -117,7 +117,7 @@ export const useProvideAuth = (): AuthStateContext => {
|
||||
navigate(
|
||||
userData.scope !== USER_ACCOUNT
|
||||
? ROUTE_INTERNAL_ACCOUNTS
|
||||
: `${ROUTE_INTERNAL_ACCOUNTS}/${userData.account_sid}/edit`
|
||||
: `${ROUTE_INTERNAL_ACCOUNTS}/${userData.account_sid}/edit`,
|
||||
);
|
||||
}
|
||||
})
|
||||
@@ -132,7 +132,7 @@ export const useProvideAuth = (): AuthStateContext => {
|
||||
navigate(
|
||||
userData.scope !== USER_ACCOUNT
|
||||
? ROUTE_INTERNAL_ACCOUNTS
|
||||
: `${ROUTE_INTERNAL_ACCOUNTS}/${userData.account_sid}/edit`
|
||||
: `${ROUTE_INTERNAL_ACCOUNTS}/${userData.account_sid}/edit`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export const genericAction = (state: State, action: Action<keyof State>) => {
|
||||
|
||||
export const serviceProvidersAction = (
|
||||
state: State,
|
||||
action: Action<keyof State>
|
||||
action: Action<keyof State>,
|
||||
) => {
|
||||
// Sorts for consistent list view
|
||||
action.payload = (<ServiceProvider[]>action.payload).sort(sortLocaleName);
|
||||
@@ -33,7 +33,7 @@ export const serviceProvidersAction = (
|
||||
const serviceProvider = action.payload.find(
|
||||
(sp: ServiceProvider) =>
|
||||
sp.service_provider_sid ===
|
||||
state.currentServiceProvider?.service_provider_sid
|
||||
state.currentServiceProvider?.service_provider_sid,
|
||||
);
|
||||
// The `serviceProvider` will be undefined if this is after a DELETE
|
||||
// For this case we want to just reset to the first provider in the list
|
||||
@@ -51,7 +51,7 @@ export const serviceProvidersAction = (
|
||||
|
||||
export const currentServiceProviderAction = (
|
||||
state: State,
|
||||
action: Action<keyof State>
|
||||
action: Action<keyof State>,
|
||||
) => {
|
||||
// Set MS Teams Tenants ACL condition
|
||||
state.accessControl.hasMSTeamsFqdn = (<ServiceProvider>action.payload)
|
||||
|
||||
@@ -109,7 +109,7 @@ const storeLocation = "location";
|
||||
export const setLocation = () => {
|
||||
return localStorage.setItem(
|
||||
storeLocation,
|
||||
window.location.pathname.split("/")[2]
|
||||
window.location.pathname.split("/")[2],
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -105,6 +105,10 @@ fieldset {
|
||||
}
|
||||
}
|
||||
|
||||
&--blur {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
label {
|
||||
@include ui-mixins.m();
|
||||
@include ui-mixins.font-medium();
|
||||
@@ -227,7 +231,7 @@ fieldset {
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
margin-top: ui-vars.$px02;
|
||||
|
||||
> div:last-child {
|
||||
@@ -294,3 +298,53 @@ fieldset {
|
||||
.bucket_tag {
|
||||
@extend .lcr;
|
||||
}
|
||||
|
||||
.customVoice {
|
||||
padding: ui-vars.$px02;
|
||||
border-radius: ui-vars.$px01;
|
||||
border: 2px solid ui-vars.$grey;
|
||||
max-width: ui-vars.$width-mobile;
|
||||
position: relative;
|
||||
|
||||
> div {
|
||||
display: grid;
|
||||
grid-gap: ui-vars.$px02;
|
||||
align-items: center;
|
||||
|
||||
&:nth-child(1) {
|
||||
grid-template-columns: [col] 100%;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
grid-template-columns: [col] calc(40% - #{ui-vars.$px02 * 2}) [col] 60%;
|
||||
margin-top: ui-vars.$px02;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
grid-template-columns: [col] 100%;
|
||||
margin-top: ui-vars.$px02;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
grid-template-columns: [col] 100%;
|
||||
margin-top: ui-vars.$px02;
|
||||
}
|
||||
}
|
||||
|
||||
> button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 50%;
|
||||
transform: translate3d(50%, 50%, 0);
|
||||
|
||||
@include mixins.small() {
|
||||
top: auto;
|
||||
bottom: auto;
|
||||
transform: none;
|
||||
position: relative;
|
||||
margin-top: ui-vars.$px02;
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,14 +29,14 @@ import type { UserData } from "src/store/types";
|
||||
import { getQueryFilter } from "src/store/localStore";
|
||||
|
||||
export const hasValue = <Type>(
|
||||
variable: Type | null | undefined
|
||||
variable: Type | null | undefined,
|
||||
): variable is NonNullable<Type> => {
|
||||
return variable !== null && variable !== undefined;
|
||||
};
|
||||
|
||||
export const hasLength = <Type>(
|
||||
variable: Type[] | null | undefined,
|
||||
minlength = 0
|
||||
minlength = 0,
|
||||
): variable is NonNullable<Type[]> => {
|
||||
return hasValue(variable) && variable.length > minlength;
|
||||
};
|
||||
@@ -52,7 +52,7 @@ export const isObject = (obj: unknown) => {
|
||||
|
||||
export const isValidPasswd = (
|
||||
password: string,
|
||||
passwordSettings: PasswordSettings
|
||||
passwordSettings: PasswordSettings,
|
||||
) => {
|
||||
if (passwordSettings) {
|
||||
return (
|
||||
@@ -78,14 +78,14 @@ export const isValidPort = (port: number) => {
|
||||
export const getIpValidationType = (ipv4: string): IpType => {
|
||||
const type =
|
||||
/^((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])$/.test(
|
||||
ipv4.trim()
|
||||
ipv4.trim(),
|
||||
)
|
||||
? IP
|
||||
: /^([a-zA-Z0-9][^.]*)(\.[^.]+){2,}$/.test(ipv4.trim())
|
||||
? FQDN
|
||||
: /^([a-zA-Z][^.]*)(\.[^.]+)$/.test(ipv4.trim())
|
||||
? FQDN_TOP_LEVEL
|
||||
: INVALID;
|
||||
? FQDN
|
||||
: /^([a-zA-Z][^.]*)(\.[^.]+)$/.test(ipv4.trim())
|
||||
? FQDN_TOP_LEVEL
|
||||
: INVALID;
|
||||
return type;
|
||||
};
|
||||
|
||||
@@ -114,16 +114,16 @@ export const getHumanDateTime = (date: string, fallbackText = "Never used") => {
|
||||
currDate.setHours(0, 0, 0, 0);
|
||||
argDate.setHours(0, 0, 0, 0);
|
||||
const daysDiff = Math.round(
|
||||
(currDate.getTime() - argDate.getTime()) / 1000 / 60 / 60 / 24
|
||||
(currDate.getTime() - argDate.getTime()) / 1000 / 60 / 60 / 24,
|
||||
);
|
||||
|
||||
return daysDiff > 1
|
||||
? `${daysDiff} days ago`
|
||||
: daysDiff === 1
|
||||
? "Yesterday"
|
||||
: daysDiff === 0
|
||||
? "Today"
|
||||
: fallbackText;
|
||||
? "Yesterday"
|
||||
: daysDiff === 0
|
||||
? "Today"
|
||||
: fallbackText;
|
||||
};
|
||||
|
||||
export const formatPhoneNumber = (number: string) => {
|
||||
@@ -150,7 +150,7 @@ export const formatTime = (seconds: number) => {
|
||||
|
||||
export const sortLocaleName = (
|
||||
a: Required<{ name: string }>,
|
||||
b: Required<{ name: string }>
|
||||
b: Required<{ name: string }>,
|
||||
) => a.name.localeCompare(b.name);
|
||||
|
||||
export const getUserScope = (user: User): UserScopes => {
|
||||
@@ -172,7 +172,7 @@ export const isUserAccountScope = (accountSid: string, user?: UserData) => {
|
||||
|
||||
export const checkSelectOptions = (
|
||||
user?: UserData,
|
||||
resource?: SpeechCredential | Carrier
|
||||
resource?: SpeechCredential | Carrier,
|
||||
) => {
|
||||
if (user?.scope === USER_ACCOUNT) {
|
||||
if (!resource) {
|
||||
@@ -203,7 +203,7 @@ export const sortUsersAlpha = (a: User, b: User) => {
|
||||
|
||||
export const filterScopeOptions = (
|
||||
optionArray: SelectorOptions[],
|
||||
user: UserData
|
||||
user: UserData,
|
||||
) => {
|
||||
if (user.scope === USER_SP) {
|
||||
return optionArray.filter((option) => option.value !== USER_ADMIN);
|
||||
|
||||
@@ -18,7 +18,7 @@ const fuzzyMatch = (patterns: string[], items: string[]) => {
|
||||
|
||||
export const useFilteredResults = <Type>(
|
||||
rawFilter: string,
|
||||
rawCollection: Type[] | undefined
|
||||
rawCollection: Type[] | undefined,
|
||||
) => {
|
||||
const splitFilter = useMemo(() => rawFilter.split(" "), [rawFilter]);
|
||||
const filteredCollection = useMemo(() => {
|
||||
|
||||
@@ -10,7 +10,7 @@ export const useMobileMedia = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const mql = window.matchMedia(
|
||||
`(max-width: ${getCssVar("--mobile-media")})`
|
||||
`(max-width: ${getCssVar("--mobile-media")})`,
|
||||
);
|
||||
|
||||
mql.addEventListener("change", handleMedia);
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { IMessage } from "src/store/types";
|
||||
export const useRedirect = <Type>(
|
||||
collection: Type[] | undefined,
|
||||
redirect: string,
|
||||
message: IMessage
|
||||
message: IMessage,
|
||||
) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export const useScopedRedirect = (
|
||||
redirect: string,
|
||||
user?: UserData,
|
||||
message?: IMessage,
|
||||
data?: Account | User | Application | Carrier | SpeechCredential
|
||||
data?: Account | User | Application | Carrier | SpeechCredential,
|
||||
) => {
|
||||
const navigate = useNavigate();
|
||||
const currentServiceProvider = useSelectState("currentServiceProvider");
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface ACLGetIMessage {
|
||||
|
||||
export const withAccessControl = (
|
||||
acl: keyof ACL,
|
||||
getMessage: ACLGetIMessage
|
||||
getMessage: ACLGetIMessage,
|
||||
) => {
|
||||
return function WithAccessControl(Component: React.ComponentType) {
|
||||
return function ComponentWithAccessControl(props: PassthroughProps) {
|
||||
|
||||
136
src/vendor/index.tsx
vendored
136
src/vendor/index.tsx
vendored
@@ -1,11 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import type {
|
||||
VendorOptions,
|
||||
SynthesisVendors,
|
||||
RecognizerVendors,
|
||||
RegionVendors,
|
||||
} from "./types";
|
||||
import type { VendorOptions, RegionVendors } from "./types";
|
||||
|
||||
export const LANG_EN_US = "en-US";
|
||||
export const ELEVENLABS_LANG_EN = "en";
|
||||
@@ -23,6 +18,11 @@ export const VENDOR_SONIOX = "soniox";
|
||||
export const VENDOR_CUSTOM = "custom";
|
||||
export const VENDOR_COBALT = "cobalt";
|
||||
export const VENDOR_ELEVENLABS = "elevenlabs";
|
||||
export const VENDOR_ASSEMBLYAI = "assemblyai";
|
||||
export const VENDOR_WHISPER = "whisper";
|
||||
export const VENDOR_PLAYHT = "playht";
|
||||
export const VENDOR_RIMELABS = "rimelabs";
|
||||
export const VENDOR_VERBIO = "verbio";
|
||||
|
||||
export const vendors: VendorOptions[] = [
|
||||
{
|
||||
@@ -73,8 +73,47 @@ export const vendors: VendorOptions[] = [
|
||||
name: "ElevenLabs",
|
||||
value: VENDOR_ELEVENLABS,
|
||||
},
|
||||
{
|
||||
name: "AssemblyAI",
|
||||
value: VENDOR_ASSEMBLYAI,
|
||||
},
|
||||
{
|
||||
name: "Whisper",
|
||||
value: VENDOR_WHISPER,
|
||||
},
|
||||
{
|
||||
name: "PlayHT",
|
||||
value: VENDOR_PLAYHT,
|
||||
},
|
||||
{
|
||||
name: "RimeLabs",
|
||||
value: VENDOR_RIMELABS,
|
||||
},
|
||||
{
|
||||
name: "Verbio",
|
||||
value: VENDOR_VERBIO,
|
||||
},
|
||||
].sort((a, b) => a.name.localeCompare(b.name)) as VendorOptions[];
|
||||
|
||||
export const AWS_CREDENTIAL_ACCESS_KEY = "access_key";
|
||||
export const AWS_CREDENTIAL_IAM_ASSUME_ROLE = "assume_role";
|
||||
export const AWS_INSTANCE_PROFILE = "instance_profile";
|
||||
|
||||
export const AWS_CREDENTIAL_TYPES = [
|
||||
{
|
||||
name: "AWS access key",
|
||||
value: AWS_CREDENTIAL_ACCESS_KEY,
|
||||
},
|
||||
{
|
||||
name: "AWS assume role",
|
||||
value: AWS_CREDENTIAL_IAM_ASSUME_ROLE,
|
||||
},
|
||||
{
|
||||
name: "AWS instance profile",
|
||||
value: AWS_INSTANCE_PROFILE,
|
||||
},
|
||||
];
|
||||
|
||||
export const useRegionVendors = () => {
|
||||
const [regions, setRegions] = useState<RegionVendors>();
|
||||
|
||||
@@ -98,7 +137,7 @@ export const useRegionVendors = () => {
|
||||
ibm: ibmRegions,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return function cleanup() {
|
||||
@@ -108,86 +147,3 @@ export const useRegionVendors = () => {
|
||||
|
||||
return regions;
|
||||
};
|
||||
|
||||
export const useSpeechVendors = () => {
|
||||
const [speech, setSpeech] = useState<{
|
||||
synthesis?: SynthesisVendors;
|
||||
recognizers?: RecognizerVendors;
|
||||
}>({});
|
||||
|
||||
useEffect(() => {
|
||||
let ignore = false;
|
||||
|
||||
Promise.all([
|
||||
import("./speech-recognizer/aws-speech-recognizer-lang"),
|
||||
import("./speech-recognizer/google-speech-recognizer-lang"),
|
||||
import("./speech-recognizer/ms-speech-recognizer-lang"),
|
||||
import("./speech-recognizer/nuance-speech-recognizer-lang"),
|
||||
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-recognizer/cobalt-speech-recognizer-lang"),
|
||||
import("./speech-synthesis/aws-speech-synthesis-lang"),
|
||||
import("./speech-synthesis/google-speech-synthesis-lang"),
|
||||
import("./speech-synthesis/ms-speech-synthesis-lang"),
|
||||
import("./speech-synthesis/wellsaid-speech-synthesis-lang"),
|
||||
import("./speech-synthesis/nuance-speech-synthesis-lang"),
|
||||
import("./speech-synthesis/ibm-speech-synthesis-lang"),
|
||||
import("./speech-synthesis/nvidia-speech-synthesis-lang"),
|
||||
import("./speech-synthesis/elevellabs-speech-synthesis-lang"),
|
||||
]).then(
|
||||
([
|
||||
{ default: awsRecognizer },
|
||||
{ default: googleRecognizer },
|
||||
{ default: msRecognizer },
|
||||
{ default: nuanceRecognizer },
|
||||
{ default: deepgramRecognizer },
|
||||
{ default: ibmRecognizer },
|
||||
{ default: nvidiaRecognizer },
|
||||
{ default: sonioxRecognizer },
|
||||
{ default: cobaltRecognizer },
|
||||
{ default: awsSynthesis },
|
||||
{ default: googleSynthesis },
|
||||
{ default: msSynthesis },
|
||||
{ default: wellsaidSynthesis },
|
||||
{ default: nuanceSynthesis },
|
||||
{ default: ibmSynthesis },
|
||||
{ default: nvidiaynthesis },
|
||||
{ default: elevenLabsSynthesis },
|
||||
]) => {
|
||||
if (!ignore) {
|
||||
setSpeech({
|
||||
synthesis: {
|
||||
aws: awsSynthesis,
|
||||
google: googleSynthesis,
|
||||
microsoft: msSynthesis,
|
||||
wellsaid: wellsaidSynthesis,
|
||||
nuance: nuanceSynthesis,
|
||||
ibm: ibmSynthesis,
|
||||
nvidia: nvidiaynthesis,
|
||||
elevenlabs: elevenLabsSynthesis,
|
||||
},
|
||||
recognizers: {
|
||||
aws: awsRecognizer,
|
||||
google: googleRecognizer,
|
||||
microsoft: msRecognizer,
|
||||
nuance: nuanceRecognizer,
|
||||
deepgram: deepgramRecognizer,
|
||||
ibm: ibmRecognizer,
|
||||
nvidia: nvidiaRecognizer,
|
||||
soniox: sonioxRecognizer,
|
||||
cobalt: cobaltRecognizer,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return function cleanup() {
|
||||
ignore = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return speech;
|
||||
};
|
||||
|
||||
8
src/vendor/regions/ms-azure-regions.ts
vendored
8
src/vendor/regions/ms-azure-regions.ts
vendored
@@ -9,6 +9,10 @@ export const regions: Region[] = [
|
||||
name: "Brazil - South (brazilsouth)",
|
||||
value: "brazilsouth",
|
||||
},
|
||||
{
|
||||
name: "Qatar - Central (qatarcentral)",
|
||||
value: "qatarcentral",
|
||||
},
|
||||
{
|
||||
name: "Canada - Central (canadacentral)",
|
||||
value: "canadacentral",
|
||||
@@ -53,6 +57,10 @@ export const regions: Region[] = [
|
||||
name: "Norway - East (norwayeast)",
|
||||
value: "norwayeast",
|
||||
},
|
||||
{
|
||||
name: "Sweden - Central (swedencentral)",
|
||||
value: "swedencentral",
|
||||
},
|
||||
{
|
||||
name: "South Africa - North (southafricanorth)",
|
||||
value: "southafricanorth",
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { Language } from "../types";
|
||||
|
||||
export const languages: Language[] = [
|
||||
{ name: "Australian English", code: "en-AU" },
|
||||
{ name: "British English", code: "en-GB" },
|
||||
{ name: "US English", code: "en-US" },
|
||||
{ name: "French", code: "fr-FR" },
|
||||
{ name: "Canadian French", code: "fr-CA" },
|
||||
{ name: "German", code: "de-DE" },
|
||||
{ name: "Italian", code: "it-IT" },
|
||||
{ name: "US Spanish", code: "es-US" },
|
||||
];
|
||||
|
||||
export default languages;
|
||||
@@ -1,30 +0,0 @@
|
||||
import type { Language } from "../types";
|
||||
|
||||
export const languages: Language[] = [
|
||||
{
|
||||
name: "English US",
|
||||
code: "en_US-8khz",
|
||||
},
|
||||
{
|
||||
name: "English UK",
|
||||
code: "en_UK-8khz",
|
||||
},
|
||||
{
|
||||
name: "Spanish",
|
||||
code: "es_xx-8khz",
|
||||
},
|
||||
{
|
||||
name: "French",
|
||||
code: "fr_fr-8khz",
|
||||
},
|
||||
{
|
||||
name: "Russian",
|
||||
code: "ru_ru-8khz",
|
||||
},
|
||||
{
|
||||
name: "Portuguese",
|
||||
code: "pt_br-8khz",
|
||||
},
|
||||
];
|
||||
|
||||
export default languages;
|
||||
@@ -1,126 +0,0 @@
|
||||
import type { Language } from "../types";
|
||||
|
||||
export const languages: Language[] = [
|
||||
{
|
||||
name: "Chinese - general",
|
||||
code: "zh",
|
||||
},
|
||||
{
|
||||
name: "Chinese (China)",
|
||||
code: "zh-CN",
|
||||
},
|
||||
{
|
||||
name: "Chinese (Taiwan)",
|
||||
code: "zh-TW",
|
||||
},
|
||||
{
|
||||
name: "Dutch - general",
|
||||
code: "nl",
|
||||
},
|
||||
{
|
||||
name: "English - general",
|
||||
code: "en",
|
||||
},
|
||||
{
|
||||
name: "English (Australia)",
|
||||
code: "en-AU",
|
||||
},
|
||||
{
|
||||
name: "English (United Kingdom)",
|
||||
code: "en-GB",
|
||||
},
|
||||
{
|
||||
name: "English (India)",
|
||||
code: "en-IN",
|
||||
},
|
||||
{
|
||||
name: "English (New Zealand)",
|
||||
code: "en-NZ",
|
||||
},
|
||||
{
|
||||
name: "English (United States)",
|
||||
code: "en-US",
|
||||
},
|
||||
{
|
||||
name: "French - general",
|
||||
code: "fr",
|
||||
},
|
||||
{
|
||||
name: "French (Canada)",
|
||||
code: "fr-CA",
|
||||
},
|
||||
{
|
||||
name: "German - general",
|
||||
code: "de",
|
||||
},
|
||||
{
|
||||
name: "Hindi - general",
|
||||
code: "hi",
|
||||
},
|
||||
{
|
||||
name: "Hindi (Roman Script)",
|
||||
code: "hi-Latin",
|
||||
},
|
||||
{
|
||||
name: "Indonesian - general",
|
||||
code: "in",
|
||||
},
|
||||
{
|
||||
name: "Italian - general",
|
||||
code: "it",
|
||||
},
|
||||
{
|
||||
name: "Japanese - general",
|
||||
code: "ja",
|
||||
},
|
||||
{
|
||||
name: "Korean - general",
|
||||
code: "ko",
|
||||
},
|
||||
{
|
||||
name: "Norwegian - general",
|
||||
code: "no",
|
||||
},
|
||||
{
|
||||
name: "Polish - general",
|
||||
code: "pl",
|
||||
},
|
||||
{
|
||||
name: "Portuguese - general",
|
||||
code: "pt",
|
||||
},
|
||||
{
|
||||
name: "Portuguese (Brazil)",
|
||||
code: "pt-BR",
|
||||
},
|
||||
{
|
||||
name: "Portuguese (Portugal)",
|
||||
code: "pt-PT",
|
||||
},
|
||||
{
|
||||
name: "Russian - general",
|
||||
code: "ru",
|
||||
},
|
||||
{
|
||||
name: "Spanish - general",
|
||||
code: "es",
|
||||
},
|
||||
{
|
||||
name: "Spanish (Latin America)",
|
||||
code: "es-419",
|
||||
},
|
||||
{
|
||||
name: "Swedish - general",
|
||||
code: "sv",
|
||||
},
|
||||
{
|
||||
name: "Turkish - general",
|
||||
code: "tr",
|
||||
},
|
||||
{
|
||||
name: "Ukrainian - general",
|
||||
code: "uk",
|
||||
},
|
||||
];
|
||||
|
||||
export default languages;
|
||||
@@ -1,134 +0,0 @@
|
||||
import type { Language } from "../types";
|
||||
|
||||
export const languages: Language[] = [
|
||||
{ name: "Afrikaans (South Africa)", code: "af-ZA" },
|
||||
{ name: "Albanian (Albania)", code: "sq-AL" },
|
||||
{ name: "Amharic (Ethiopia)", code: "am-ET" },
|
||||
{ name: "Arabic (Algeria)", code: "ar-DZ" },
|
||||
{ name: "Arabic (Bahrain)", code: "ar-BH" },
|
||||
{ name: "Arabic (Egypt)", code: "ar-EG" },
|
||||
{ name: "Arabic (Iraq)", code: "ar-IQ" },
|
||||
{ name: "Arabic (Israel)", code: "ar-IL" },
|
||||
{ name: "Arabic (Jordan)", code: "ar-JO" },
|
||||
{ name: "Arabic (Kuwait)", code: "ar-KW" },
|
||||
{ name: "Arabic (Lebanon)", code: "ar-LB" },
|
||||
{ name: "Arabic (Morocco)", code: "ar-MA" },
|
||||
{ name: "Arabic (Oman)", code: "ar-OM" },
|
||||
{ name: "Arabic (Qatar)", code: "ar-QA" },
|
||||
{ name: "Arabic (Saudi Arabia)", code: "ar-SA" },
|
||||
{ name: "Arabic (State of Palestine)", code: "ar-PS" },
|
||||
{ name: "Arabic (Tunisia)", code: "ar-TN" },
|
||||
{ name: "Arabic (United Arab Emirates)", code: "ar-AE" },
|
||||
{ name: "Armenian (Armenia)", code: "hy-AM" },
|
||||
{ name: "Azerbaijani (Azerbaijan)", code: "az-AZ" },
|
||||
{ name: "Basque (Spain)", code: "eu-ES" },
|
||||
{ name: "Bengali (Bangladesh)", code: "bn-BD" },
|
||||
{ name: "Bengali (India)", code: "bn-IN" },
|
||||
{ name: "Bulgarian (Bulgaria)", code: "bg-BG" },
|
||||
{ name: "Burmese (Myanmar)", code: "my-MM" },
|
||||
{ name: "Catalan (Spain)", code: "ca-ES" },
|
||||
{ name: "Chinese, Cantonese (Traditional, Hong Kong)", code: "yue-Hant-HK" },
|
||||
{ name: "Chinese, Mandarin (Simplified, China)", code: "zh" },
|
||||
{ name: "Chinese, Mandarin (Simplified, Hong Kong)", code: "zh-HK" },
|
||||
{ name: "Chinese, Mandarin (Simplified, Taiwan)", code: "zh-TW" },
|
||||
{ name: "Croatian (Croatia)", code: "hr-HR" },
|
||||
{ name: "Czech (Czech Republic)", code: "cs-CZ" },
|
||||
{ name: "Danish (Denmark)", code: "da-DK" },
|
||||
{ name: "Dutch (Belgium)", code: "nl-BE" },
|
||||
{ name: "Dutch (Netherlands)", code: "nl-NL" },
|
||||
{ name: "English (Australia)", code: "en-AU" },
|
||||
{ name: "English (Canada)", code: "en-CA" },
|
||||
{ name: "English (Ghana)", code: "en-GH" },
|
||||
{ name: "English (India)", code: "en-IN" },
|
||||
{ name: "English (Ireland)", code: "en-IE" },
|
||||
{ name: "English (Kenya)", code: "en-KE" },
|
||||
{ name: "English (New Zealand)", code: "en-NZ" },
|
||||
{ name: "English (Nigeria)", code: "en-NG" },
|
||||
{ name: "English (Philippines)", code: "en-PH" },
|
||||
{ name: "English (Singapore)", code: "en-SG" },
|
||||
{ name: "English (South Africa)", code: "en-ZA" },
|
||||
{ name: "English (Tanzania)", code: "en-TZ" },
|
||||
{ name: "English (United Kingdom)", code: "en-GB" },
|
||||
{ name: "English (United States)", code: "en-US" },
|
||||
{ name: "Estonian (Estonia)", code: "et-EE" },
|
||||
{ name: "Filipino (Philippines)", code: "fil-PH" },
|
||||
{ name: "Finnish (Finland)", code: "fi-FI" },
|
||||
{ name: "French (Canada)", code: "fr-CA" },
|
||||
{ name: "French (France)", code: "fr-FR" },
|
||||
{ name: "Galician (Spain)", code: "gl-ES" },
|
||||
{ name: "Georgian (Georgia)", code: "ka-GE" },
|
||||
{ name: "German (Germany)", code: "de-DE" },
|
||||
{ name: "Greek (Greece)", code: "el-GR" },
|
||||
{ name: "Gujarati (India)", code: "gu-IN" },
|
||||
{ name: "Hebrew (Israel)", code: "he-IL" },
|
||||
{ name: "Hindi (India)", code: "hi-IN" },
|
||||
{ name: "Hungarian (Hungary)", code: "hu-HU" },
|
||||
{ name: "Icelandic (Iceland)", code: "is-IS" },
|
||||
{ name: "Indonesian (Indonesia)", code: "id-ID" },
|
||||
{ name: "Italian (Italy)", code: "it-IT" },
|
||||
{ name: "Japanese (Japan)", code: "ja-JP" },
|
||||
{ name: "Javanese (Indonesia)", code: "jv-ID" },
|
||||
{ name: "Kannada (India)", code: "kn-IN" },
|
||||
{ name: "Khmer (Cambodia)", code: "km-KH" },
|
||||
{ name: "Korean (South Korea)", code: "ko-KR" },
|
||||
{ name: "Lao (Laos)", code: "lo-LA" },
|
||||
{ name: "Latvian (Latvia)", code: "lv-LV" },
|
||||
{ name: "Lithuanian (Lithuania)", code: "lt-LT" },
|
||||
{ name: "Macedonian (North Macedonia)", code: "mk-MK" },
|
||||
{ name: "Malay (Malaysia)", code: "ms-MY" },
|
||||
{ name: "Malayalam (India)", code: "ml-IN" },
|
||||
{ name: "Marathi (India)", code: "mr-IN" },
|
||||
{ name: "Mongolian (Mongolia)", code: "mn-MN" },
|
||||
{ name: "Nepali (Nepal)", code: "ne-NP" },
|
||||
{ name: "Norwegian Bokmål (Norway)", code: "nb-NO" },
|
||||
{ name: "Persian (Iran)", code: "fa-IR" },
|
||||
{ name: "Polish (Poland)", code: "pl-PL" },
|
||||
{ name: "Portuguese (Brazil)", code: "pt-BR" },
|
||||
{ name: "Portuguese (Portugal)", code: "pt-PT" },
|
||||
{ name: "Punjabi (Gurmukhi, India)", code: "pa-guru-IN" },
|
||||
{ name: "Romanian (Romania)", code: "ro-RO" },
|
||||
{ name: "Russian (Russia)", code: "ru-RU" },
|
||||
{ name: "Serbian (Serbia)", code: "sr-RS" },
|
||||
{ name: "Sinhala (Sri Lanka)", code: "si-LK" },
|
||||
{ name: "Slovak (Slovakia)", code: "sk-SK" },
|
||||
{ name: "Slovenian (Slovenia)", code: "sl-SI" },
|
||||
{ name: "Spanish (Argentina)", code: "es-AR" },
|
||||
{ name: "Spanish (Bolivia)", code: "es-BO" },
|
||||
{ name: "Spanish (Chile)", code: "es-CL" },
|
||||
{ name: "Spanish (Colombia)", code: "es-CO" },
|
||||
{ name: "Spanish (Costa Rica)", code: "es-CR" },
|
||||
{ name: "Spanish (Dominican Republic)", code: "es-DO" },
|
||||
{ name: "Spanish (Ecuador)", code: "es-EC" },
|
||||
{ name: "Spanish (El Salvador)", code: "es-SV" },
|
||||
{ name: "Spanish (Guatemala)", code: "es-GT" },
|
||||
{ name: "Spanish (Honduras)", code: "es-HN" },
|
||||
{ name: "Spanish (Mexico)", code: "es-MX" },
|
||||
{ name: "Spanish (Nicaragua)", code: "es-NI" },
|
||||
{ name: "Spanish (Panama)", code: "es-PA" },
|
||||
{ name: "Spanish (Paraguay)", code: "es-PY" },
|
||||
{ name: "Spanish (Peru)", code: "es-PE" },
|
||||
{ name: "Spanish (Puerto Rico)", code: "es-PR" },
|
||||
{ name: "Spanish (Spain)", code: "es-ES" },
|
||||
{ name: "Spanish (United States)", code: "es-US" },
|
||||
{ name: "Spanish (Uruguay)", code: "es-UY" },
|
||||
{ name: "Spanish (Venezuela)", code: "es-VE" },
|
||||
{ name: "Sundanese (Indonesia)", code: "su-ID" },
|
||||
{ name: "Swahili (Kenya)", code: "sw-KE" },
|
||||
{ name: "Swahili (Tanzania)", code: "sw-TZ" },
|
||||
{ name: "Swedish (Sweden)", code: "sv-SE" },
|
||||
{ name: "Tamil (India)", code: "ta-IN" },
|
||||
{ name: "Tamil (Malaysia)", code: "ta-MY" },
|
||||
{ name: "Tamil (Singapore)", code: "ta-SG" },
|
||||
{ name: "Tamil (Sri Lanka)", code: "ta-LK" },
|
||||
{ name: "Telugu (India)", code: "te-IN" },
|
||||
{ name: "Thai (Thailand)", code: "th-TH" },
|
||||
{ name: "Turkish (Turkey)", code: "tr-TR" },
|
||||
{ name: "Ukrainian (Ukraine)", code: "uk-UA" },
|
||||
{ name: "Urdu (India)", code: "ur-IN" },
|
||||
{ name: "Urdu (Pakistan)", code: "ur-PK" },
|
||||
{ name: "Uzbek (Uzbekistan)", code: "uz-UZ" },
|
||||
{ name: "Vietnamese (Vietnam)", code: "vi-VN" },
|
||||
{ name: "Zulu (South Africa)", code: "zu-ZA" },
|
||||
];
|
||||
|
||||
export default languages;
|
||||
@@ -1,86 +0,0 @@
|
||||
import type { Language } from "../types";
|
||||
|
||||
export const languages: Language[] = [
|
||||
{
|
||||
name: "Arabic (Modern Standard)",
|
||||
code: "ar-MS_Telephony",
|
||||
},
|
||||
{
|
||||
name: "Chinese (Mandarin)",
|
||||
code: "zh-CN_Telephony",
|
||||
},
|
||||
{
|
||||
name: "Czech ",
|
||||
code: "cs-CZ_Telephony",
|
||||
},
|
||||
{
|
||||
name: "Dutch (Belgian)",
|
||||
code: "nl-BE_Telephony",
|
||||
},
|
||||
{
|
||||
name: "Dutch (Netherlands)",
|
||||
code: "nl-NL_Telephony",
|
||||
},
|
||||
{
|
||||
name: "English (all supported dialects)",
|
||||
code: "en-WW_Medical_Telephony",
|
||||
},
|
||||
{
|
||||
name: "English (Australian)",
|
||||
code: "en-AU_Telephony",
|
||||
},
|
||||
{
|
||||
name: "English (Indian)",
|
||||
code: "en-IN_Telephony",
|
||||
},
|
||||
{
|
||||
name: "English (United Kingdom)",
|
||||
code: "en-GB_Telephony",
|
||||
},
|
||||
{
|
||||
name: "English (United States)",
|
||||
code: "en-US_Telephony",
|
||||
},
|
||||
{
|
||||
name: "French (Canadian)",
|
||||
code: "fr-CA_Telephony",
|
||||
},
|
||||
{
|
||||
name: "French (France)",
|
||||
code: "fr-FR_Telephony",
|
||||
},
|
||||
{
|
||||
name: "German",
|
||||
code: "de-DE_Telephony",
|
||||
},
|
||||
{
|
||||
name: "Hindi (Indian)",
|
||||
code: "hi-IN_Telephony",
|
||||
},
|
||||
{
|
||||
name: "Italian",
|
||||
code: "it-IT_Telephony",
|
||||
},
|
||||
{
|
||||
name: "Korean",
|
||||
code: "ko-KR_Telephony",
|
||||
},
|
||||
{
|
||||
name: "Portuguese (Brazilian)",
|
||||
code: "pt-BR_Telephony",
|
||||
},
|
||||
{
|
||||
name: "Spanish (Mexican)",
|
||||
code: "es-LA_Telephony",
|
||||
},
|
||||
{
|
||||
name: "Spanish (Castilian)",
|
||||
code: "es-ES_Telephony",
|
||||
},
|
||||
{
|
||||
name: "Swedish ",
|
||||
code: "sv-SE_Telephony",
|
||||
},
|
||||
];
|
||||
|
||||
export default languages;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user