mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2026-02-09 02:29:45 +00:00
Compare commits
16 Commits
v0.8.5-17
...
snyk-fix-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b061b982f | ||
|
|
e8355a1dd3 | ||
|
|
8be61ddfad | ||
|
|
05c1d9efaa | ||
|
|
01a5476dfe | ||
|
|
9d2fee64e6 | ||
|
|
3a87f5f1c2 | ||
|
|
a991b56a4e | ||
|
|
6e14207327 | ||
|
|
8d8d46e76e | ||
|
|
7f72d739cd | ||
|
|
c804d60664 | ||
|
|
df3fc8f2b7 | ||
|
|
65e5b511c3 | ||
|
|
dc519bdef9 | ||
|
|
af1ba3a15c |
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') }}
|
||||
|
||||
@@ -6,7 +6,7 @@ RUN npm install
|
||||
RUN npm run build
|
||||
RUN npm prune
|
||||
|
||||
FROM node:18.14.1-alpine as webapp
|
||||
FROM node:18.19.1-alpine as webapp
|
||||
RUN apk add curl
|
||||
WORKDIR /opt/app
|
||||
COPY . /opt/app
|
||||
|
||||
@@ -51,6 +51,11 @@ export interface WaveSurferTtsLatencyResult {
|
||||
isCached: string;
|
||||
}
|
||||
|
||||
export interface WaveSurferGatherSpeechVerbHookLatencyResult {
|
||||
statusCode: number;
|
||||
latency: string;
|
||||
}
|
||||
|
||||
export interface WaveSurferDtmfResult {
|
||||
dtmf: string;
|
||||
duration: string;
|
||||
|
||||
@@ -419,6 +419,8 @@ export interface SpeechCredential {
|
||||
model_id: null | string;
|
||||
model: null | string;
|
||||
options: null | string;
|
||||
deepgram_stt_uri: null | string;
|
||||
deepgram_stt_use_tls: number;
|
||||
}
|
||||
|
||||
export interface Alert {
|
||||
|
||||
@@ -11,6 +11,7 @@ type CheckzoneProps = {
|
||||
hidden?: boolean;
|
||||
children: React.ReactNode;
|
||||
initialCheck: boolean;
|
||||
disabled?: boolean;
|
||||
handleChecked?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
};
|
||||
|
||||
@@ -28,6 +29,7 @@ export const Checkzone = forwardRef<CheckzoneRef, CheckzoneProps>(
|
||||
children,
|
||||
initialCheck,
|
||||
handleChecked,
|
||||
disabled = false,
|
||||
}: CheckzoneProps,
|
||||
ref
|
||||
) => {
|
||||
@@ -51,6 +53,7 @@ export const Checkzone = forwardRef<CheckzoneRef, CheckzoneProps>(
|
||||
<label>
|
||||
<div className="label-container">
|
||||
<input
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
type="checkbox"
|
||||
name={name}
|
||||
|
||||
@@ -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,7 +86,10 @@ 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[]>(
|
||||
|
||||
@@ -386,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) {
|
||||
@@ -462,12 +509,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
|
||||
if (application.data.speech_recognizer_language)
|
||||
setRecogLang(application.data.speech_recognizer_language);
|
||||
if (application.data.speech_synthesis_label) {
|
||||
setSynthLabel(application.data.speech_synthesis_label);
|
||||
}
|
||||
if (application.data.speech_recognizer_label) {
|
||||
setRecogLabel(application.data.speech_recognizer_label);
|
||||
}
|
||||
|
||||
if (application.data.use_for_fallback_speech) {
|
||||
setUseForFallbackSpeech(application.data.use_for_fallback_speech > 0);
|
||||
setInitalCheckFallbackSpeech(
|
||||
@@ -485,11 +527,7 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
application.data.fallback_speech_recognizer_language
|
||||
);
|
||||
}
|
||||
if (application.data.fallback_speech_recognizer_label) {
|
||||
setFallbackSpeechRecognizerLabel(
|
||||
application.data.fallback_speech_recognizer_label
|
||||
);
|
||||
}
|
||||
|
||||
if (application.data.fallback_speech_synthesis_vendor) {
|
||||
setFallbackSpeechSynthsisVendor(
|
||||
application.data
|
||||
@@ -506,11 +544,6 @@ export const ApplicationForm = ({ application }: ApplicationFormProps) => {
|
||||
application.data.fallback_speech_synthesis_voice
|
||||
);
|
||||
}
|
||||
if (application.data.fallback_speech_synthesis_label) {
|
||||
setFallbackSpeechSynthsisLabel(
|
||||
application.data.fallback_speech_synthesis_label
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [application]);
|
||||
|
||||
|
||||
@@ -99,27 +99,37 @@ export const SpeechProviderSelection = ({
|
||||
SelectorOption[]
|
||||
>([]);
|
||||
|
||||
const currentVendor = useRef("");
|
||||
|
||||
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 (
|
||||
!user ||
|
||||
!synthVendor ||
|
||||
(user?.scope === USER_ADMIN && !serviceProviderSid)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
currentVendor.current = synthVendor;
|
||||
if (
|
||||
!user ||
|
||||
(user?.scope === USER_ADMIN &&
|
||||
!currentServiceProvider?.service_provider_sid)
|
||||
) {
|
||||
return;
|
||||
// just execute last change
|
||||
if (ttsEffectTimer.current) {
|
||||
clearTimeout(ttsEffectTimer.current);
|
||||
}
|
||||
configSynthesis();
|
||||
}, [synthVendor, synthLabel, currentServiceProvider]);
|
||||
|
||||
ttsEffectTimer.current = setTimeout(() => {
|
||||
configSynthesis();
|
||||
}, 200);
|
||||
}, [synthVendor, synthLabel, serviceProviderSid]);
|
||||
|
||||
// Get Recognizer languages and voices
|
||||
useEffect(() => {
|
||||
@@ -130,19 +140,27 @@ export const SpeechProviderSelection = ({
|
||||
}
|
||||
if (
|
||||
!user ||
|
||||
(user?.scope === USER_ADMIN &&
|
||||
!currentServiceProvider?.service_provider_sid)
|
||||
!recogVendor ||
|
||||
(user?.scope === USER_ADMIN && !serviceProviderSid)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
configRecognizer();
|
||||
}, [recogVendor, recogLabel, currentServiceProvider]);
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -161,45 +179,46 @@ export const SpeechProviderSelection = ({
|
||||
if (synthesisSupportedLanguagesAndVoices) {
|
||||
// Extract Voice
|
||||
const voicesOpts =
|
||||
synthesisSupportedLanguagesAndVoices.tts.find((lang) => {
|
||||
synthesisSupportedLanguagesAndVoices.tts?.find((lang) => {
|
||||
if (synthVendor === VENDOR_ELEVENLABS && lang.voices.length > 0) {
|
||||
return true;
|
||||
}
|
||||
return lang.value === synthLang;
|
||||
})?.voices || [];
|
||||
setSynthesisVoiceOptions(voicesOpts);
|
||||
|
||||
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 (currentVendor.current !== VENDOR_GOOGLE) {
|
||||
return;
|
||||
}
|
||||
const customVOices = json.map((v) => ({
|
||||
name: `${v.name} (Custom)`,
|
||||
value: `custom_${v.google_custom_voice_sid}`,
|
||||
}));
|
||||
setSynthesisGoogleCustomVoiceOptions(customVOices);
|
||||
setSynthesisVoiceOptions([...customVOices, ...voicesOpts]);
|
||||
if (customVOices.length > 0) {
|
||||
setSynthVoice(customVOices[0].value);
|
||||
}
|
||||
});
|
||||
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]);
|
||||
}, [
|
||||
synthLang,
|
||||
synthesisSupportedLanguagesAndVoices,
|
||||
synthesisGoogleCustomVoiceOptions,
|
||||
]);
|
||||
|
||||
const configSynthesis = () => {
|
||||
getSpeechSupportedLanguagesAndVoices(
|
||||
currentServiceProvider?.service_provider_sid,
|
||||
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) {
|
||||
@@ -219,26 +238,27 @@ export const SpeechProviderSelection = ({
|
||||
setSynthesisLanguageOptions(langOpts);
|
||||
|
||||
// Default setting
|
||||
if (synthVendor === VENDOR_GOOGLE && synthLang === LANG_EN_US) {
|
||||
setSynthVoice(LANG_EN_US_STANDARD_C);
|
||||
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);
|
||||
const newLang = json.tts.find(
|
||||
(lang) => lang.value === ELEVENLABS_LANG_EN
|
||||
);
|
||||
if (newLang) {
|
||||
setSynthVoice(newLang.voices[0].value);
|
||||
}
|
||||
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);
|
||||
setSynthVoice(newLang!.voices[0].value);
|
||||
updateTtsVoice(newLang!.voices[0].value);
|
||||
return;
|
||||
}
|
||||
/** Google and AWS have different language lists */
|
||||
@@ -246,28 +266,57 @@ export const SpeechProviderSelection = ({
|
||||
let newLang = json.tts.find((lang) => lang.value === synthLang);
|
||||
|
||||
if (newLang) {
|
||||
setSynthVoice(newLang.voices[0].value);
|
||||
updateTtsVoice(newLang.voices[0].value);
|
||||
return;
|
||||
}
|
||||
|
||||
newLang = json.tts.find((lang) => lang.value === LANG_EN_US);
|
||||
|
||||
setSynthLang(LANG_EN_US);
|
||||
setSynthVoice(newLang!.voices[0].value);
|
||||
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(
|
||||
currentServiceProvider?.service_provider_sid,
|
||||
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,
|
||||
@@ -276,8 +325,12 @@ export const SpeechProviderSelection = ({
|
||||
setRecogLanguageOptions(langOpts);
|
||||
|
||||
/**When vendor is custom, Language is input by user */
|
||||
if (recogVendor.toString() === VENDOR_CUSTOM) return;
|
||||
|
||||
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);
|
||||
@@ -287,10 +340,10 @@ export const SpeechProviderSelection = ({
|
||||
!newLang
|
||||
) {
|
||||
setRecogLang(LANG_EN_US);
|
||||
}
|
||||
// Default colbalt language
|
||||
if (recogVendor === VENDOR_COBALT) {
|
||||
} else if (recogVendor === VENDOR_COBALT && !newLang) {
|
||||
setRecogLang(LANG_COBALT_EN_US);
|
||||
} else if (langOpts.length && !newLang) {
|
||||
setRecogLang(langOpts[0].value);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -314,6 +367,7 @@ export const SpeechProviderSelection = ({
|
||||
)}
|
||||
onChange={(e) => {
|
||||
const vendor = e.target.value as keyof SynthesisVendors;
|
||||
shouldUpdateTtsVoice.current = true;
|
||||
setSynthVendor(vendor);
|
||||
setSynthLabel("");
|
||||
setSynthesisLanguageOptions([]);
|
||||
@@ -358,6 +412,7 @@ export const SpeechProviderSelection = ({
|
||||
value={synthLang}
|
||||
options={synthesisLanguageOptions}
|
||||
onChange={(e) => {
|
||||
shouldUpdateTtsVoice.current = true;
|
||||
const language = e.target.value;
|
||||
setSynthLang(language);
|
||||
|
||||
@@ -461,6 +516,7 @@ export const SpeechProviderSelection = ({
|
||||
)}
|
||||
onChange={(e) => {
|
||||
const vendor = e.target.value as keyof RecognizerVendors;
|
||||
shouldUpdateSttLanguage.current = true;
|
||||
setRecogVendor(vendor);
|
||||
setRecogLabel("");
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
JaegerRoot,
|
||||
JaegerSpan,
|
||||
WaveSurferDtmfResult,
|
||||
WaveSurferGatherSpeechVerbHookLatencyResult,
|
||||
WaveSurferSttResult,
|
||||
WaveSurferTtsLatencyResult,
|
||||
} from "src/api/jaeger-types";
|
||||
@@ -45,6 +46,11 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
|
||||
const [waveSurferTtsLatencyData, setWaveSurferTtsLatencyData] =
|
||||
useState<WaveSurferTtsLatencyResult | null>();
|
||||
|
||||
const [
|
||||
waveSurferGatherSpeechVerbHookLatencyData,
|
||||
setWaveSurferGatherSpeechVerbHookLatencyData,
|
||||
] = useState<WaveSurferGatherSpeechVerbHookLatencyResult | null>();
|
||||
const [regionChecked, setRegionChecked] = useState(false);
|
||||
|
||||
const wavesurferId = `wavesurfer--${call_sid}`;
|
||||
@@ -199,7 +205,7 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
color: "rgba(255, 255, 0, 0.55)",
|
||||
drag: false,
|
||||
resize: false,
|
||||
content: `${(end - endSpeechTime).toFixed(2)} sec`,
|
||||
content: `${(end - endSpeechTime).toFixed(2)}s`,
|
||||
});
|
||||
|
||||
changeRegionMouseStyle(latencyRegion, channel);
|
||||
@@ -253,11 +259,18 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
if (!r) {
|
||||
const start =
|
||||
(s.startTimeUnixNano - startPoint.startTimeUnixNano) / 1_000_000_000;
|
||||
const end =
|
||||
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,
|
||||
@@ -266,16 +279,15 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
color: "rgba(255, 155, 0, 0.55)",
|
||||
drag: false,
|
||||
resize: false,
|
||||
content: `${(end - start).toFixed(2)} sec`,
|
||||
content: createMultiLineTextElement(`${(end - start).toFixed(2)}s`),
|
||||
});
|
||||
|
||||
changeRegionMouseStyle(latencyRegion, 2);
|
||||
console.log(ttsCache.value.boolValue);
|
||||
changeRegionMouseStyle(latencyRegion, 1);
|
||||
|
||||
latencyRegion.on("click", () => {
|
||||
setWaveSurferTtsLatencyData({
|
||||
vendor: ttsVendor.value.stringValue,
|
||||
latency: `${(end - start).toFixed(2)} sec`,
|
||||
latency: `${(end - start).toFixed(2)}s`,
|
||||
isCached: String(ttsCache.value.boolValue),
|
||||
});
|
||||
});
|
||||
@@ -284,6 +296,53 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
@@ -296,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
|
||||
@@ -306,16 +366,35 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -535,7 +614,7 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div>Overlay STT, TTS latency and DTMF events</div>
|
||||
<div>Show latencies</div>
|
||||
</label>
|
||||
</div>
|
||||
{waveSurferRegionData && (
|
||||
@@ -670,6 +749,38 @@ export const Player = ({ call }: PlayerProps) => {
|
||||
</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("")}
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
isUserAccountScope,
|
||||
isNotBlank,
|
||||
hasLength,
|
||||
hasValue,
|
||||
} from "src/utils";
|
||||
import { getObscuredGoogleServiceKey } from "./utils";
|
||||
import { CredentialStatus } from "./status";
|
||||
@@ -144,6 +145,13 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
|
||||
const [optionsInitialChecked, setOptionsInitialChecked] = useState(false);
|
||||
const [options, setOptions] = useState("");
|
||||
const [tmpOptions, setTmpOptions] = useState("");
|
||||
const [deepgramSttUri, setDeepgramSttUri] = useState("");
|
||||
const [tmpDeepgramSttUri, setTmpDeepgramSttUri] = useState("");
|
||||
const [deepgramSttUseTls, setDeepgramSttUseTls] = useState(false);
|
||||
const [tmpDeepgramSttUseTls, setTmpDeepgramSttUseTls] = useState(false);
|
||||
const [initialDeepgramOnpremCheck, setInitialDeepgramOnpremCheck] =
|
||||
useState(false);
|
||||
const [isDeepgramOnpremEnabled, setIsDeepgramOnpremEnabled] = useState(false);
|
||||
|
||||
const handleFile = (file: File) => {
|
||||
const handleError = () => {
|
||||
@@ -292,6 +300,10 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
|
||||
...(vendor === VENDOR_ELEVENLABS && {
|
||||
options: options || null,
|
||||
}),
|
||||
...(vendor === VENDOR_DEEPGRAM && {
|
||||
deepgram_stt_uri: deepgramSttUri || null,
|
||||
deepgram_stt_use_tls: deepgramSttUseTls ? 1 : 0,
|
||||
}),
|
||||
};
|
||||
|
||||
if (credential && credential.data) {
|
||||
@@ -544,6 +556,16 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
|
||||
setUseCustomVoicesCheck(json.length > 0);
|
||||
});
|
||||
}
|
||||
if (credential?.data?.deepgram_stt_uri) {
|
||||
setDeepgramSttUri(credential.data.deepgram_stt_uri);
|
||||
}
|
||||
if (credential?.data?.deepgram_stt_use_tls) {
|
||||
setDeepgramSttUseTls(
|
||||
credential?.data?.deepgram_stt_use_tls > 0 ? true : false
|
||||
);
|
||||
}
|
||||
setInitialDeepgramOnpremCheck(hasValue(credential?.data?.deepgram_stt_uri));
|
||||
setIsDeepgramOnpremEnabled(hasValue(credential?.data?.deepgram_stt_uri));
|
||||
}, [credential]);
|
||||
|
||||
const updateCustomVoices = (
|
||||
@@ -1112,7 +1134,6 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
|
||||
</fieldset>
|
||||
)}
|
||||
{(vendor === VENDOR_WELLSAID ||
|
||||
vendor === VENDOR_DEEPGRAM ||
|
||||
vendor === VENDOR_ASSEMBLYAI ||
|
||||
vendor == VENDOR_ELEVENLABS ||
|
||||
vendor === VENDOR_WHISPER ||
|
||||
@@ -1132,6 +1153,22 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
|
||||
/>
|
||||
</fieldset>
|
||||
)}
|
||||
{vendor === VENDOR_DEEPGRAM && (
|
||||
<fieldset>
|
||||
<label htmlFor={`${vendor}_apikey`}>
|
||||
API key{!isDeepgramOnpremEnabled && <span>*</span>}
|
||||
</label>
|
||||
<Passwd
|
||||
id={`${vendor}_apikey`}
|
||||
required={!isDeepgramOnpremEnabled}
|
||||
name={`${vendor}_apikey`}
|
||||
placeholder="API key"
|
||||
value={apiKey ? getObscuredSecret(apiKey) : apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
disabled={credential ? true : false}
|
||||
/>
|
||||
</fieldset>
|
||||
)}
|
||||
{(vendor == VENDOR_ELEVENLABS || vendor == VENDOR_WHISPER) &&
|
||||
ttsModels.length > 0 && (
|
||||
<fieldset>
|
||||
@@ -1324,6 +1361,57 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
|
||||
/>
|
||||
</fieldset>
|
||||
)}
|
||||
{vendor === VENDOR_DEEPGRAM && (
|
||||
<fieldset>
|
||||
<Checkzone
|
||||
disabled={hasValue(credential)}
|
||||
hidden
|
||||
name="use_hosted_deepgram_service"
|
||||
label="Use on-prem Deepgram container"
|
||||
initialCheck={initialDeepgramOnpremCheck}
|
||||
handleChecked={(e) => {
|
||||
// setInitialDeepgramOnpremCheck(!e.target.checked);
|
||||
setIsDeepgramOnpremEnabled(e.target.checked);
|
||||
if (e.target.checked) {
|
||||
if (tmpDeepgramSttUri) {
|
||||
setDeepgramSttUri(tmpDeepgramSttUri);
|
||||
}
|
||||
if (tmpDeepgramSttUseTls) {
|
||||
setDeepgramSttUseTls(tmpDeepgramSttUseTls);
|
||||
}
|
||||
} else {
|
||||
setTmpDeepgramSttUri(deepgramSttUri);
|
||||
setDeepgramSttUri("");
|
||||
setTmpDeepgramSttUseTls(deepgramSttUseTls);
|
||||
setDeepgramSttUseTls(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<label htmlFor="deepgram_uri_for_tts">
|
||||
Container URI<span>*</span>
|
||||
</label>
|
||||
<input
|
||||
id="deepgram_uri_for_tts"
|
||||
required
|
||||
type="text"
|
||||
name="deepgram_uri_for_tts"
|
||||
placeholder="Container URI for TTS"
|
||||
value={deepgramSttUri}
|
||||
onChange={(e) => setDeepgramSttUri(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="deepgram_tts_use_tls" className="chk">
|
||||
<input
|
||||
id="deepgram_tts_use_tls"
|
||||
name="deepgram_tts_use_tls"
|
||||
type="checkbox"
|
||||
onChange={(e) => setDeepgramSttUseTls(e.target.checked)}
|
||||
defaultChecked={deepgramSttUseTls}
|
||||
/>
|
||||
<div>Use TLS</div>
|
||||
</label>
|
||||
</Checkzone>
|
||||
</fieldset>
|
||||
)}
|
||||
{vendor === VENDOR_MICROSOFT && (
|
||||
<React.Fragment>
|
||||
<fieldset>
|
||||
|
||||
Reference in New Issue
Block a user