Compare commits

...

15 Commits

Author SHA1 Message Date
Hoan Luu Huu
c33eb46ce0 soundhound speech credential support audio endpoint (#582)
* soubound speech credential support audio endpoint

* wip
2025-11-28 21:48:13 -05:00
Hoan Luu Huu
f003c158dc fix outbound call routing race condition show default lcr route set that user cannot delete (#577) 2025-11-18 07:52:28 -05:00
Sam Machin
b1ddaf230d require IP auth trunk to have either inbound or outbound carrier (#579)
also cleaned up wordig to be consistent `IP Trunk` not `Static IP Whitelist`
2025-11-10 10:29:45 -05:00
Hoan Luu Huu
0260b1ec8b Inbound and outbound sipgateway can be duplicated (#576) 2025-11-08 08:49:00 -05:00
Anton Voylenko
1c1f97f045 chore: bump node version (#575) 2025-11-04 19:33:41 -05:00
Hoan Luu Huu
e6c5a18c87 fixed reg trunk validation cannot move tab and focus to missing fields (#574)
* fixed reg trunk validation cannot move tab and focus to missing fields

* fixed reg trunk validation cannot move tab and focus to missing fields

* wip
2025-10-27 07:21:39 -04:00
Hoan Luu Huu
19742ab67e fixed cannot saved auth trunk (#573) 2025-10-24 07:22:36 -04:00
Hoan Luu Huu
53d0c0b510 Carrier change for trunk type (#564)
* support carrier credential authentication

* wip

* wip

* wip

* wip

* wip

* change trunk type to selector

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip
2025-10-21 06:49:25 -04:00
Hoan Luu Huu
7a0eb71bae support gladia stt (#572) 2025-10-20 04:47:55 -04:00
Hoan Luu Huu
6aae8d9930 support soundhound stt (#567)
* support houndify stt

* wip

* wip
2025-10-14 00:52:06 -04:00
Hoan Luu Huu
a70a1bf614 support elevenlabs different endpoint (#571)
* support elevenlabs different endpoint

* wip
2025-10-09 08:20:39 -04:00
Dave Horton
975a787f1e review Preview now that Flux is GA (#570) 2025-10-04 20:12:15 -04:00
Hoan Luu Huu
46e220f28b support deepgram flux (#569)
* support deepgram flux

* wip
2025-10-03 10:10:13 -04:00
Hoan Luu Huu
6836a99635 add special field for Carriers in env vars (#561)
* add special field for Carriers in env vars

* wip

* wip
2025-09-05 08:04:24 -04:00
Hoan Luu Huu
f7f4a2e7b1 cannot delete carrier because of undefined lcrs list (#563) 2025-09-01 08:12:31 -04:00
16 changed files with 1141 additions and 666 deletions

2
.env
View File

@@ -1,4 +1,4 @@
#VITE_API_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

View File

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

86
package-lock.json generated
View File

@@ -46,13 +46,13 @@
"nanoid": "^5.1.5",
"prettier": "^3.2.5",
"sass": "^1.89.2",
"serve": "^14.2.4",
"serve": "^14.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.4.4",
"vite": "^6.0.1"
"vite": "^6.4.1"
},
"engines": {
"node": ">=14.18"
"node": ">=18"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -2196,19 +2196,6 @@
"integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==",
"dev": true
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"dev": true,
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
@@ -3265,6 +3252,7 @@
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
"integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
"dev": true,
"license": "MIT",
"dependencies": {
"mime-db": ">= 1.43.0 < 2"
},
@@ -3273,37 +3261,30 @@
}
},
"node_modules/compression": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
"integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"dev": true,
"license": "MIT",
"dependencies": {
"accepts": "~1.3.5",
"bytes": "3.0.0",
"compressible": "~2.0.16",
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"on-headers": "~1.0.2",
"safe-buffer": "5.1.2",
"negotiator": "~0.6.4",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/compression/node_modules/bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
"dev": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/compression/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
@@ -3312,13 +3293,8 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
"node_modules/compression/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/concat-map": {
"version": "0.0.1",
@@ -6911,10 +6887,11 @@
"dev": true
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -7071,10 +7048,11 @@
}
},
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -8047,10 +8025,11 @@
}
},
"node_modules/serve": {
"version": "14.2.4",
"resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz",
"integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==",
"version": "14.2.5",
"resolved": "https://registry.npmjs.org/serve/-/serve-14.2.5.tgz",
"integrity": "sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@zeit/schemas": "2.36.0",
"ajv": "8.12.0",
@@ -8059,7 +8038,7 @@
"chalk": "5.0.1",
"chalk-template": "0.4.0",
"clipboardy": "3.0.0",
"compression": "1.7.4",
"compression": "1.8.1",
"is-port-reachable": "4.0.0",
"serve-handler": "6.1.6",
"update-check": "1.5.4"
@@ -9104,10 +9083,11 @@
}
},
"node_modules/vite": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",

View File

@@ -5,7 +5,7 @@
"license": "MIT",
"type": "module",
"engines": {
"node": ">=14.18"
"node": ">=18"
},
"contributors": [
{
@@ -77,10 +77,10 @@
"nanoid": "^5.1.5",
"prettier": "^3.2.5",
"sass": "^1.89.2",
"serve": "^14.2.4",
"serve": "^14.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.4.4",
"vite": "^6.0.1"
"vite": "^6.4.1"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --max-warnings=0",

View File

@@ -131,7 +131,7 @@ export const DEFAULT_WEBHOOK: WebHook = {
};
/** Default SIP/SMPP Gateways */
export const DEFAULT_SIP_GATEWAY: SipGateway = {
export const DEFAULT_SIP_INBOUND_GATEWAY: SipGateway = {
voip_carrier_sid: "",
ipv4: "",
port: 5060,
@@ -348,6 +348,12 @@ export const DTMF_TYPE_SELECTION: SelectorOptions[] = [
{ name: "Tones", value: "tones" },
];
export const TRUNK_TYPE_SELECTION: SelectorOptions[] = [
{ name: "IP Trunk", value: "static_ip" },
{ name: "Auth Trunk", value: "auth" },
{ name: "Registration Trunk", value: "reg" },
];
/** Available webhook methods */
export const WEBHOOK_METHODS: WebhookOption[] = [
{

View File

@@ -1,4 +1,10 @@
import type { Language, Model, Vendor, VoiceLanguage } from "src/vendor/types";
import type {
JambonzResourceOptions,
Language,
Model,
Vendor,
VoiceLanguage,
} from "src/vendor/types";
/** Simple types */
@@ -413,6 +419,7 @@ export interface SpeechCredential {
custom_stt_endpoint: null | string;
client_id: null | string;
client_secret: null | string;
client_key: null | string;
secret: null | string;
nuance_tts_uri: null | string;
nuance_stt_uri: null | string;
@@ -442,6 +449,8 @@ export interface SpeechCredential {
playht_tts_uri: null | string;
resemble_tts_uri: null | string;
resemble_tts_use_tls: number;
api_uri: null | string;
houndify_server_uri: null | string;
}
export interface Alert {
@@ -461,6 +470,8 @@ export interface CarrierRegisterStatus {
export type DtmfType = "rfc2833" | "tones" | "info";
export type TrunkType = "static_ip" | "auth" | "reg";
export interface Carrier {
voip_carrier_sid: string;
name: string;
@@ -489,6 +500,7 @@ export interface Carrier {
register_status: CarrierRegisterStatus;
dtmf_type: DtmfType;
outbound_sip_proxy: string | null;
trunk_type: TrunkType;
}
export interface PredefinedCarrier extends Carrier {
@@ -825,6 +837,8 @@ export interface AppEnvProperty {
obscure?: boolean;
uiHint?: "input" | "textarea" | "filepicker";
enum?: string[];
jambonzResource?: "carriers";
jambonzResourceOptions?: JambonzResourceOptions[];
}
export interface AppEnv {

View File

@@ -25,6 +25,7 @@ import {
useServiceProviderData,
useApiData,
getAppEnvSchema,
getSPVoipCarriers,
} from "src/api";
import {
ROUTE_INTERNAL_ACCOUNTS,
@@ -588,6 +589,51 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
setFallbackSpeechRecognizerLabel(tmp);
};
const fetchAppEnvJambonzResources = async (appEnv: AppEnv) => {
if (appEnv) {
const promises = Object.entries(appEnv).map(async ([key, value]) => {
const { jambonzResource } = value;
switch (jambonzResource) {
case "carriers":
const carriers = await getSPVoipCarriers(
currentServiceProvider?.service_provider_sid || "",
{
page: 1,
page_size: 10000,
...(user?.account_sid && {
account_sid: user.account_sid,
}),
},
);
if (carriers.json.total) {
return {
key,
jambonzResourceOptions: carriers.json.data.map((carrier) => ({
name: carrier.name,
value: carrier.name,
})),
};
}
break;
default:
break;
}
return { key, jambonzResourceOptions: null };
});
const results = await Promise.all(promises);
// Merge the results back into appEnv
results.forEach(({ key, jambonzResourceOptions }) => {
if (jambonzResourceOptions) {
appEnv[key].jambonzResourceOptions = jambonzResourceOptions;
}
});
}
return appEnv;
};
useEffect(() => {
if (callWebhook && callWebhook.url) {
// Clear any existing timeout to prevent multiple requests
@@ -599,19 +645,26 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
appEnvTimeoutRef.current = setTimeout(() => {
getAppEnvSchema(callWebhook.url)
.then(({ json }) => {
setAppEnv(json);
const defaultEnvVars = Object.keys(json).reduce((acc, key) => {
const value = json[key];
if (value?.default) {
return { ...acc, [key]: value.default };
}
return acc;
}, {});
// fetch app env jambonz_resource
fetchAppEnvJambonzResources(json).then((updatedEnv) => {
setAppEnv(updatedEnv);
const defaultEnvVars = Object.keys(updatedEnv).reduce(
(acc, key) => {
const value = updatedEnv[key];
if (value?.default) {
return { ...acc, [key]: value.default };
}
return acc;
},
{},
);
setEnvVars((prev) => ({
...defaultEnvVars,
...(prev || {}),
}));
setEnvVars((prev) => ({
...defaultEnvVars,
...(prev || {}),
}));
});
// Default value
})
.catch((error) => {
setMessage(error.msg);
@@ -873,9 +926,13 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
};
const isDropdown =
webhook.webhookEnv![key].type === "string" &&
(webhook.webhookEnv![key].enum?.length || 0) >
0;
(webhook.webhookEnv![key].type === "string" &&
(webhook.webhookEnv![key].enum?.length ||
0) > 0) ||
hasLength(
webhook.webhookEnv![key]
.jambonzResourceOptions,
);
const textAreaSpecificProps = {
rows: 6,
@@ -888,15 +945,19 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
? ObscureInput
: webhook.webhookEnv![key].uiHint || "input";
if (isDropdown) {
const options =
webhook.webhookEnv![key]
.jambonzResourceOptions ||
webhook.webhookEnv![key].enum!.map(
(option) => ({
name: option,
value: option,
}),
);
return (
<Selector
{...commonProps}
options={webhook.webhookEnv![
key
].enum!.map((option) => ({
name: option,
value: option,
}))}
options={options}
/>
);
}

View File

@@ -36,8 +36,9 @@ import {
VENDOR_RIMELABS,
VENDOR_OPENAI,
VENDOR_INWORLD,
VENDOR_DEEPGRAM_RIVER,
VENDOR_DEEPGRAM_FLUX,
VENDOR_RESEMBLE,
VENDOR_HOUNDIFY,
} from "src/vendor";
import {
LabelOptions,
@@ -370,7 +371,7 @@ export const SpeechProviderSelection = ({
};
const configRecognizer = () => {
if (recogVendor === VENDOR_DEEPGRAM_RIVER) {
if (recogVendor === VENDOR_DEEPGRAM_FLUX) {
return;
}
getSpeechSupportedLanguagesAndVoices(
@@ -433,7 +434,8 @@ export const SpeechProviderSelection = ({
vendor.value !== VENDOR_SPEECHMATICS &&
vendor.value !== VENDOR_CUSTOM &&
vendor.value !== VENDOR_OPENAI &&
vendor.value !== VENDOR_DEEPGRAM_RIVER &&
vendor.value !== VENDOR_DEEPGRAM_FLUX &&
vendor.value !== VENDOR_HOUNDIFY &&
vendor.value !== VENDOR_COBALT,
)}
onChange={(e) => {
@@ -616,7 +618,7 @@ export const SpeechProviderSelection = ({
)}
{recogVendor &&
!recogVendor.toString().startsWith(VENDOR_CUSTOM) &&
recogVendor !== VENDOR_DEEPGRAM_RIVER &&
recogVendor !== VENDOR_DEEPGRAM_FLUX &&
recogLang && (
<>
<label htmlFor="recognizer_lang">Language</label>

View File

@@ -4,7 +4,7 @@ import { P } from "@jambonz/ui-kit";
import { Modal, ModalClose } from "src/components";
import { getFetch, getLcrRoutes, getLcrs } from "src/api";
import { API_PHONE_NUMBERS } from "src/api/constants";
import { formatPhoneNumber, hasLength } from "src/utils";
import { formatPhoneNumber, hasLength, hasValue } from "src/utils";
import type { Carrier, Lcr, PhoneNumber } from "src/api/types";
@@ -63,7 +63,8 @@ export const DeleteCarrier = ({
),
);
setLcrs(fetchedLcrs);
// Only set LCRs if they are not empty
setLcrs(fetchedLcrs.filter((p) => hasValue(p)));
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -67,9 +67,6 @@ export const LcrForm = ({ lcrDataMap, lcrRouteDataMap }: LcrFormProps) => {
const [accountSid, setAccountSid] = useState("");
const [isActive, setIsActive] = useState(true);
const [lcrRoutes, setLcrRoutes] = useState<LcrRoute[]>([LCR_ROUTE_TEMPLATE]);
const [previousLcrRoutes, setPreviousLcrRoutes] = useState<LcrRoute[]>([
LCR_ROUTE_TEMPLATE,
]);
const [previouseLcr, setPreviousLcr] = useState<Lcr | null>();
const [accounts] = useServiceProviderData<Account[]>("Accounts");
const [lcrForDelete, setLcrForDelete] = useState<Lcr | null>();
@@ -127,38 +124,35 @@ export const LcrForm = ({ lcrDataMap, lcrRouteDataMap }: LcrFormProps) => {
}, [lcrDataMap?.data, previouseLcr]);
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
) {
// Only process when both lcrDataMap and lcrRouteDataMap are available
if (lcrRouteDataMap && lcrRouteDataMap.data && lcrDataMap?.data) {
const defaultCarrierSetEntrySid =
lcrDataMap.data.default_carrier_set_entry_sid;
// Find and store default route information
lcrRouteDataMap.data.forEach((route) => {
route.lcr_carrier_set_entries?.forEach((entry) => {
if (entry.lcr_carrier_set_entry_sid === defaultCarrierSetEntrySid) {
setDefaultLcrCarrier(entry.voip_carrier_sid || defaultCarrier);
setDefaultLcrCarrierSetEntrySid(
entry.lcr_carrier_set_entry_sid || null,
);
default_lcr_route_sid = entry.lcr_route_sid || "";
setDefaultLcrRoute(lr);
setDefaultLcrRoute(route);
}
});
});
}
if (lcrRouteDataMap && lcrRouteDataMap.data)
setLcrRoutes(
lcrRouteDataMap.data.filter(
(route) => route.lcr_route_sid !== default_lcr_route_sid,
),
);
}, [lcrRouteDataMap?.data]);
// Filter out routes that contain the default carrier set entry
const filteredRoutes = lcrRouteDataMap.data.filter((route) => {
return !route.lcr_carrier_set_entries?.some(
(entry) =>
entry.lcr_carrier_set_entry_sid === defaultCarrierSetEntrySid,
);
});
setLcrRoutes(filteredRoutes);
}
}, [lcrRouteDataMap?.data, lcrDataMap?.data]);
const addLcrRoutes = () => {
const newLcrRoute = LCR_ROUTE_TEMPLATE;

View File

@@ -53,8 +53,10 @@ import {
VENDOR_VOXIST,
VENDOR_OPENAI,
VENDOR_INWORLD,
VENDOR_DEEPGRAM_RIVER,
VENDOR_DEEPGRAM_FLUX,
VENDOR_RESEMBLE,
VENDOR_HOUNDIFY,
VENDOR_GLADIA,
} from "src/vendor";
import { MSG_REQUIRED_FIELDS } from "src/constants";
import {
@@ -108,6 +110,13 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
const { toastError, toastSuccess } = useToast();
const navigate = useNavigate();
const user = useSelectState("user");
// ElevenLabs API URI options
const ELEVENLABS_API_URI_OPTIONS = [
{ name: "US", value: "api.elevenlabs.io" },
{ name: "EU", value: "api.eu.residency.elevenlabs.io" },
{ name: "IN", value: "api.in.residency.elevenlabs.io" },
];
const currentServiceProvider = useSelectState("currentServiceProvider");
const regions = useRegionVendors();
const [accounts] = useServiceProviderData<Account[]>("Accounts");
@@ -121,11 +130,13 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
);
const [region, setRegion] = useState("");
const [apiKey, setApiKey] = useState("");
const [apiUri, setApiUri] = useState("api.elevenlabs.io");
const [userId, setUserId] = useState("");
const [accessKeyId, setAccessKeyId] = useState("");
const [secretAccessKey, setSecretAccessKey] = useState("");
const [clientId, setClientId] = useState("");
const [secretKey, setSecretKey] = useState("");
const [clientKey, setClientKey] = useState("");
const [clientSecret, setClientSecret] = useState("");
const [googleServiceKey, setGoogleServiceKey] =
useState<GoogleServiceKey | null>(null);
@@ -214,6 +225,7 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
useState(false);
const [resembleTtsUseTls, setResembleTtsUseTls] = useState(false);
const [tmpResembleTtsUseTls, setTmpResembleTtsUseTls] = useState(false);
const [houndifyServerUri, setHoundifyServerUri] = useState("");
const handleFile = (file: File) => {
const handleError = () => {
setGoogleServiceKey(null);
@@ -447,6 +459,12 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
nuance_tts_uri: onPremNuanceTtsUrl || null,
nuance_stt_uri: onPremNuanceSttUrl || null,
}),
...(vendor === VENDOR_HOUNDIFY && {
client_id: clientId || null,
client_key: clientKey || null,
user_id: userId || null,
houndify_server_uri: houndifyServerUri || null,
}),
...(vendor === VENDOR_COBALT && {
cobalt_server_uri: cobaltServerUri || null,
}),
@@ -456,6 +474,9 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
vendor === VENDOR_RIMELABS) && {
model_id: ttsModelId || null,
}),
...(vendor === VENDOR_ELEVENLABS && {
api_uri: apiUri || null,
}),
...((vendor === VENDOR_ELEVENLABS ||
vendor === VENDOR_PLAYHT ||
vendor === VENDOR_INWORLD ||
@@ -492,6 +513,10 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
resemble_tts_uri: resembleTtsUri || null,
resemble_tts_use_tls: resembleTtsUseTls ? 1 : 0,
}),
...(vendor === VENDOR_GLADIA && {
api_key: apiKey || null,
region: region || null,
}),
};
if (credential && credential.data) {
@@ -543,7 +568,8 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
vendor === VENDOR_CARTESIA ||
vendor === VENDOR_OPENAI ||
vendor === VENDOR_RESEMBLE ||
vendor === VENDOR_DEEPGRAM_RIVER
vendor === VENDOR_DEEPGRAM_FLUX ||
vendor === VENDOR_GLADIA
? apiKey
: null,
}),
@@ -697,6 +723,10 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
setApiKey(credential.data.api_key);
}
if (credential.data.api_uri) {
setApiUri(credential.data.api_uri);
}
if (credential.data.region) {
setRegion(credential.data.region);
}
@@ -708,6 +738,9 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
if (credential.data.client_id) {
setClientId(credential.data.client_id);
}
if (credential.data.client_key) {
setClientKey(credential.data.client_key);
}
if (credential.data.secret) {
setSecretKey(credential.data.secret);
@@ -847,6 +880,10 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
setUserId(credential.data.user_id);
}
if (credential?.data?.houndify_server_uri) {
setHoundifyServerUri(credential.data.houndify_server_uri);
}
if (credential?.data?.voice_engine) {
setTtsModelId(credential.data.voice_engine);
}
@@ -954,6 +991,9 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
setVendor(e.target.value as Lowercase<Vendor>);
setRegion("");
setApiKey("");
setApiUri(
e.target.value === VENDOR_ELEVENLABS ? "api.elevenlabs.io" : "",
);
setGoogleServiceKey(null);
}}
disabled={credential ? true : false}
@@ -1009,8 +1049,10 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
vendor !== VENDOR_COBALT &&
vendor !== VENDOR_SONIOX &&
vendor !== VENDOR_SPEECHMATICS &&
vendor !== VENDOR_DEEPGRAM_RIVER &&
vendor !== VENDOR_DEEPGRAM_FLUX &&
vendor !== VENDOR_HOUNDIFY &&
vendor !== VENDOR_OPENAI &&
vendor !== VENDOR_GLADIA &&
vendor != VENDOR_CUSTOM && (
<label htmlFor="use_for_tts" className="chk">
<input
@@ -1409,6 +1451,56 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
)}
</>
)}
{vendor === VENDOR_HOUNDIFY && (
<fieldset>
<label htmlFor="houndify_client_id">
Client ID
{!onPremNuanceSttCheck && !onPremNuanceTtsCheck && <span>*</span>}
</label>
<input
id="houndify_client_id"
required={!onPremNuanceSttCheck && !onPremNuanceTtsCheck}
type="text"
name="houndify_client_id"
placeholder="Client ID"
value={clientId}
onChange={(e) => setClientId(e.target.value)}
disabled={credential ? true : false}
/>
<label htmlFor="houndify_secret">
Client Key
{!onPremNuanceSttCheck && !onPremNuanceTtsCheck && <span>*</span>}
</label>
<Passwd
id="houndify_secret"
required={!onPremNuanceSttCheck && !onPremNuanceTtsCheck}
name="houndify_secret"
placeholder="Client Key"
value={clientKey ? getObscuredSecret(clientKey) : clientKey}
onChange={(e) => setClientKey(e.target.value)}
disabled={credential ? true : false}
/>
<label htmlFor="houndify_user_id">User ID</label>
<input
id="houndify_user_id"
type="text"
name="houndify_user_id"
placeholder="User ID"
value={userId}
onChange={(e) => setUserId(e.target.value)}
disabled={credential ? true : false}
/>
<label htmlFor="houndify_server_uri">Audio Endpoint</label>
<input
id="houndify_server_uri"
type="text"
name="houndify_server_uri"
placeholder="Audio Endpoint (optional)"
value={houndifyServerUri}
onChange={(e) => setHoundifyServerUri(e.target.value)}
/>
</fieldset>
)}
{vendor === VENDOR_NUANCE && (
<>
<fieldset>
@@ -1820,19 +1912,47 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
</fieldset>
)}
{vendor === VENDOR_ELEVENLABS && (
<fieldset>
<label htmlFor="elevenlabs_api_uri">
Data residency<span>*</span>
</label>
<Selector
id="elevenlabs_api_uri"
name="elevenlabs_api_uri"
value={apiUri}
options={ELEVENLABS_API_URI_OPTIONS}
onChange={(e) => setApiUri(e.target.value)}
required
/>
<label htmlFor={`${vendor}_apikey`}>
API key<span>*</span>
</label>
<Passwd
id={`${vendor}_apikey`}
required
name={`${vendor}_apikey`}
placeholder="API key"
value={apiKey ? getObscuredSecret(apiKey) : apiKey}
onChange={(e) => setApiKey(e.target.value)}
disabled={credential ? true : false}
/>
</fieldset>
)}
{(vendor === VENDOR_WELLSAID ||
vendor === VENDOR_ASSEMBLYAI ||
vendor === VENDOR_VOXIST ||
vendor == VENDOR_ELEVENLABS ||
vendor === VENDOR_WHISPER ||
vendor === VENDOR_RIMELABS ||
vendor === VENDOR_INWORLD ||
vendor === VENDOR_SONIOX ||
vendor === VENDOR_CARTESIA ||
vendor === VENDOR_OPENAI ||
vendor === VENDOR_DEEPGRAM_RIVER ||
vendor === VENDOR_DEEPGRAM_FLUX ||
vendor === VENDOR_RESEMBLE ||
vendor === VENDOR_SPEECHMATICS) && (
vendor === VENDOR_SPEECHMATICS ||
vendor === VENDOR_GLADIA) && (
<fieldset>
<label htmlFor={`${vendor}_apikey`}>
API key<span>*</span>

View File

@@ -218,7 +218,8 @@ fieldset {
}
}
.gateway {
.gateway,
.gateway-inbound {
padding: ui-vars.$px02;
border-radius: ui-vars.$px01;
border: 2px solid ui-vars.$grey;
@@ -284,6 +285,18 @@ fieldset {
}
}
.gateway-inbound {
> div {
&:nth-child(1) {
grid-template-columns: [col] calc(70% - #{ui-vars.$px02 * 2}) [col] 30%;
@include mixins.small() {
grid-template-columns: [col] 100%;
}
}
}
}
.lcr {
@extend .gateway;

19
src/vendor/index.tsx vendored
View File

@@ -12,7 +12,7 @@ export const VENDOR_MICROSOFT = "microsoft";
export const VENDOR_WELLSAID = "wellsaid";
export const VENDOR_NUANCE = "nuance";
export const VENDOR_DEEPGRAM = "deepgram";
export const VENDOR_DEEPGRAM_RIVER = "deepgramriver";
export const VENDOR_DEEPGRAM_FLUX = "deepgramflux";
export const VENDOR_IBM = "ibm";
export const VENDOR_NVIDIA = "nvidia";
export const VENDOR_SONIOX = "soniox";
@@ -30,6 +30,8 @@ export const VENDOR_VERBIO = "verbio";
export const VENDOR_CARTESIA = "cartesia";
export const VENDOR_OPENAI = "openai";
export const VENDOR_RESEMBLE = "resemble";
export const VENDOR_HOUNDIFY = "houndify";
export const VENDOR_GLADIA = "gladia";
export const vendors: VendorOptions[] = [
{
@@ -45,8 +47,8 @@ export const vendors: VendorOptions[] = [
value: VENDOR_DEEPGRAM,
},
{
name: "Deepgram River Preview",
value: VENDOR_DEEPGRAM_RIVER,
name: "Deepgram Flux",
value: VENDOR_DEEPGRAM_FLUX,
},
{
name: "IBM",
@@ -128,6 +130,14 @@ export const vendors: VendorOptions[] = [
name: "Resemble",
value: VENDOR_RESEMBLE,
},
{
name: "SoundHound",
value: VENDOR_HOUNDIFY,
},
{
name: "Gladia",
value: VENDOR_GLADIA,
},
].sort((a, b) => a.name.localeCompare(b.name)) as VendorOptions[];
export const AWS_CREDENTIAL_ACCESS_KEY = "access_key";
@@ -160,12 +170,14 @@ export const useRegionVendors = () => {
import("./regions/ms-azure-regions"),
import("./regions/ibm-regions"),
import("./regions/speechmatics-regions"),
import("./regions/gladia-regions"),
]).then(
([
{ default: awsRegions },
{ default: msRegions },
{ default: ibmRegions },
{ default: speechmaticsRegions },
{ default: gladiaRegions },
]) => {
if (!ignore) {
setRegions({
@@ -173,6 +185,7 @@ export const useRegionVendors = () => {
microsoft: msRegions,
ibm: ibmRegions,
speechmatics: speechmaticsRegions,
gladia: gladiaRegions,
});
}
},

14
src/vendor/regions/gladia-regions.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
import type { Region } from "../types";
export const regions: Region[] = [
{
name: "US West",
value: "us-west",
},
{
name: "EU West",
value: "eu-west",
},
];
export default regions;

14
src/vendor/types.ts vendored
View File

@@ -5,7 +5,7 @@ export type Vendor =
| "WellSaid"
| "Nuance"
| "Deepgram"
| "DeepgramRiver"
| "DeepgramFlux"
| "IBM"
| "Nvidia"
| "Soniox"
@@ -22,7 +22,9 @@ export type Vendor =
| "verbio"
| "openai"
| "Cartesia"
| "Resemble";
| "Resemble"
| "Houndify"
| "gladia";
export interface VendorOptions {
name: Vendor;
@@ -34,6 +36,11 @@ export interface LabelOptions {
value: string;
}
export interface JambonzResourceOptions {
name: string;
value: string;
}
export interface Region {
name: string;
value: string;
@@ -79,6 +86,7 @@ export interface RegionVendors {
microsoft: Region[];
ibm: Region[];
speechmatics: Region[];
gladia: Region[];
}
export interface TtsModels {
@@ -99,7 +107,7 @@ export interface RecognizerVendors {
speechmatics: Language[];
cobalt: Language[];
assemblyai: Language[];
deepgramriver: Language[];
deepgramflux: Language[];
}
export interface SynthesisVendors {