Compare commits

...

4 Commits

Author SHA1 Message Date
Hoan Luu Huu
bf87e4fb80 Feat/gh 482 (#488)
* remove messaging hook from application

* remove messaging hook from application
2025-02-26 19:02:54 -05:00
Hoan Luu Huu
b8140ba0d6 Support voip carrier sip proxy (#484)
* Support voip carrier sip proxy

* fixed review comment
2025-02-17 09:47:02 -05:00
Hoan Luu Huu
9fd847015e rime labs support new voices (#483) 2025-02-07 07:22:45 -05:00
Hoan Luu Huu
c237b7e7f2 support voxist stt (#480) 2025-02-05 08:33:12 -05:00
7 changed files with 105 additions and 32 deletions

View File

@@ -318,7 +318,6 @@ export interface Application {
app_json: null | string;
call_hook: null | WebHook;
account_sid: null | string;
messaging_hook: null | WebHook;
application_sid: string;
call_status_hook: null | WebHook;
speech_synthesis_voice: null | string;
@@ -481,6 +480,7 @@ export interface Carrier {
smpp_enquire_link_interval: number;
register_status: CarrierRegisterStatus;
dtmf_type: DtmfType;
outbound_sip_proxy: string | null;
}
export interface PredefinedCarrier extends Carrier {

View File

@@ -77,11 +77,6 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
const [tmpStatusWebhook, setTmpStatusWebhook] =
useState<WebHook>(DEFAULT_WEBHOOK);
const [initialStatusWebhook, setInitialStatusWebhook] = useState(false);
const [messageWebhook, setMessageWebhook] =
useState<WebHook>(DEFAULT_WEBHOOK);
const [tmpMessageWebhook, setTmpMessageWebhook] =
useState<WebHook>(DEFAULT_WEBHOOK);
const [initialMessageWebhook, setInitialMessageWebhook] = useState(false);
const [synthVendor, setSynthVendor] =
useState<keyof SynthesisVendors>(VENDOR_GOOGLE);
const [synthLang, setSynthLang] = useState(LANG_EN_US);
@@ -150,16 +145,6 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
initialCheck: initialStatusWebhook,
required: true,
},
{
label: "Messaging",
prefix: "message_webhook",
stateVal: messageWebhook,
tmpStateVal: tmpMessageWebhook,
stateSet: setMessageWebhook,
tmpStateSet: setTmpMessageWebhook,
initialCheck: initialMessageWebhook,
required: false,
},
];
useRedirect<Account>(
@@ -202,7 +187,6 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
app_json: applicationJson || null,
call_hook: callWebhook || null,
account_sid: accountSid || null,
messaging_hook: messageWebhook || null,
call_status_hook: statusWebhook || null,
speech_synthesis_vendor: synthVendor || null,
speech_synthesis_language: synthLang || null,
@@ -471,24 +455,9 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
else setInitialStatusWebhook(false);
}
if (application.data.messaging_hook) {
setMessageWebhook(application.data.messaging_hook);
setTmpMessageWebhook(application.data.messaging_hook);
if (
application.data.messaging_hook.username ||
application.data.messaging_hook.password
)
setInitialMessageWebhook(true);
else setInitialMessageWebhook(false);
}
if (application.data.account_sid)
setAccountSid(application.data.account_sid);
if (application.data.messaging_hook)
setMessageWebhook(application.data.messaging_hook);
if (application.data.speech_synthesis_vendor)
setSynthVendor(
application.data.speech_synthesis_vendor as keyof SynthesisVendors,

View File

@@ -31,6 +31,8 @@ import {
VENDOR_SPEECHMATICS,
VENDOR_PLAYHT,
VENDOR_CARTESIA,
VENDOR_VOXIST,
VENDOR_RIMELABS,
} from "src/vendor";
import {
LabelOptions,
@@ -291,6 +293,12 @@ export const SpeechProviderSelection = ({
updateTtsVoice(newLang!.voices[0].value);
return;
}
if (synthVendor === VENDOR_RIMELABS) {
const newLang = json.tts.find((lang) => lang.value === "eng");
setSynthLang(newLang!.value);
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);
@@ -391,6 +399,7 @@ export const SpeechProviderSelection = ({
options={ttsVendorOptions.filter(
(vendor) =>
vendor.value !== VENDOR_ASSEMBLYAI &&
vendor.value !== VENDOR_VOXIST &&
vendor.value !== VENDOR_SONIOX &&
vendor.value !== VENDOR_SPEECHMATICS &&
vendor.value !== VENDOR_CUSTOM &&

View File

@@ -119,6 +119,9 @@ export const CarrierForm = ({
const [diversion, setDiversion] = useState("");
const [initialDiversion, setInitialDiversion] = useState(false);
const [initialSipProxy, setInitialSipProxy] = useState(false);
const [outboundSipProxy, setOutboundSipProxy] = useState("");
const [smppSystemId, setSmppSystemId] = useState("");
const [smppPass, setSmppPass] = useState("");
const [smppInboundSystemId, setSmppInboundSystemId] = useState("");
@@ -142,6 +145,44 @@ export const CarrierForm = ({
const [smppInboundMessage, setSmppInboundMessage] = useState("");
const [smppOutboundMessage, setSmppOutboundMessage] = useState("");
const validateOutboundSipGateway = (gateway: string): boolean => {
/** validate outbound sip gateway that can be
* ip address
dns name
sip(s):ip address
sip(s):dns name
full sip uri
full sips uri
*/
// firstly checkig it's including sip or sips
if (
gateway.includes(":") &&
!gateway.includes("sip:") &&
!gateway.includes("sips:")
) {
return false;
}
if (gateway.includes("sip:") || gateway.includes("sips:")) {
const sipGateway = gateway.trim().split(":");
if (sipGateway.length === 2) {
const sipGatewayType = getIpValidationType(sipGateway[1]);
if (sipGatewayType === INVALID) {
return false;
}
} else {
return false;
}
}
// check IP address or domain name
else {
const sipGatewayType = getIpValidationType(gateway);
if (sipGatewayType === INVALID) {
return false;
}
}
return true;
};
const setCarrierStates = (obj: Carrier) => {
if (obj) {
setIsActive(obj.is_active);
@@ -207,6 +248,13 @@ export const CarrierForm = ({
setInitialDiversion(false);
}
if (obj.outbound_sip_proxy) {
setOutboundSipProxy(obj.outbound_sip_proxy);
setInitialSipProxy(true);
} else {
setInitialSipProxy(false);
}
if (obj.smpp_system_id) {
setSmppSystemId(obj.smpp_system_id);
}
@@ -508,6 +556,14 @@ export const CarrierForm = ({
}
}
if (
isNotBlank(outboundSipProxy) &&
!validateOutboundSipGateway(outboundSipProxy)
) {
toastError("Please provide a valid SIP Proxy domain or IP address.");
return;
}
if (currentServiceProvider) {
const carrierPayload: Partial<Carrier> = {
name: carrierName.trim(),
@@ -531,6 +587,7 @@ export const CarrierForm = ({
smpp_inbound_system_id: smppInboundSystemId.trim() || null,
smpp_inbound_password: smppInboundPass.trim() || null,
dtmf_type: dtmfType,
outbound_sip_proxy: outboundSipProxy.trim().replaceAll(" ", "") || null,
};
if (carrier && carrier.data) {
@@ -971,6 +1028,33 @@ export const CarrierForm = ({
/>
</Checkzone>
</fieldset>
<fieldset>
<Checkzone
hidden
name="outbound_sip_proxy"
label="Outbound SIP Proxy"
initialCheck={initialSipProxy}
handleChecked={(e) => {
if (!e.target.checked) {
setOutboundSipProxy("");
}
}}
>
<MS>
Send all calls to this carrier through an outbound proxy
</MS>
<input
id="outbound_sip_proxy"
name="outbound_sip_proxy"
type="text"
value={outboundSipProxy}
placeholder="Outbound Sip Proxy"
onChange={(e) => {
setOutboundSipProxy(e.target.value);
}}
/>
</Checkzone>
</fieldset>
<fieldset>
<label htmlFor="sip_gateways">
SIP gateways<span>*</span>

View File

@@ -50,6 +50,7 @@ import {
VENDOR_VERBIO,
VENDOR_SPEECHMATICS,
VENDOR_CARTESIA,
VENDOR_VOXIST,
} from "src/vendor";
import { MSG_REQUIRED_FIELDS } from "src/constants";
import {
@@ -461,6 +462,7 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
vendor === VENDOR_WELLSAID ||
vendor === VENDOR_DEEPGRAM ||
vendor === VENDOR_ASSEMBLYAI ||
vendor === VENDOR_VOXIST ||
vendor === VENDOR_SONIOX ||
vendor === VENDOR_SPEECHMATICS ||
vendor === VENDOR_ELEVENLABS ||
@@ -881,6 +883,7 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
{vendor && (
<fieldset>
{vendor !== VENDOR_ASSEMBLYAI &&
vendor !== VENDOR_VOXIST &&
vendor !== VENDOR_COBALT &&
vendor !== VENDOR_SONIOX &&
vendor !== VENDOR_SPEECHMATICS &&
@@ -1512,6 +1515,7 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
{(vendor === VENDOR_WELLSAID ||
vendor === VENDOR_ASSEMBLYAI ||
vendor === VENDOR_VOXIST ||
vendor == VENDOR_ELEVENLABS ||
vendor === VENDOR_WHISPER ||
vendor === VENDOR_PLAYHT ||

View File

@@ -20,6 +20,7 @@ export const VENDOR_CUSTOM = "custom";
export const VENDOR_COBALT = "cobalt";
export const VENDOR_ELEVENLABS = "elevenlabs";
export const VENDOR_ASSEMBLYAI = "assemblyai";
export const VENDOR_VOXIST = "voxist";
export const VENDOR_WHISPER = "whisper";
export const VENDOR_PLAYHT = "playht";
export const VENDOR_RIMELABS = "rimelabs";
@@ -83,6 +84,10 @@ export const vendors: VendorOptions[] = [
name: "AssemblyAI",
value: VENDOR_ASSEMBLYAI,
},
{
name: "Voxist",
value: VENDOR_VOXIST,
},
{
name: "Whisper",
value: VENDOR_WHISPER,

2
src/vendor/types.ts vendored
View File

@@ -13,6 +13,7 @@ export type Vendor =
| "Custom"
| "ElevenLabs"
| "assemblyai"
| "voxist"
| "whisper"
| "playht"
| "rimelabs"
@@ -109,6 +110,7 @@ export interface SynthesisVendors {
deepgram: VoiceLanguage[];
playht: VoiceLanguage[];
cartesia: VoiceLanguage[];
rimelabs: VoiceLanguage[];
}
export interface MSRawSpeech {