import React, { useState, useEffect } from "react"; import PlayIcon from "@material-ui/icons/PlayCircleFilled"; import { TextInput, SelectInput, required, useTranslate, useNotify, ReferenceInput, ReferenceField, TextField, } from "react-admin"; import { IconButton, CircularProgress } from "@material-ui/core"; import absoluteUrl from "../../../lib/absolute-url"; import TwilioLanguages from "./twilio-languages"; type TTSProvider = (voice: any, language: any, prompt: any) => Promise; const tts = async (providerId): Promise => { const r = await fetch( `/api/v1/voice/twilio/text-to-speech-token/${providerId}` ); const { token } = await r.json(); const twilioClient = await import("twilio-client"); return (voice, language, prompt): Promise => new Promise((resolve) => { if (!voice || !language || !prompt) resolve(); const Device = twilioClient.Device; const device = new Device(); const silence = `${absoluteUrl().origin}/static/silence.mp3`; device.setup(token, { codecPrefences: ["opus", "pcmu"], enableRingingState: false, fakeLocalDTMF: true, disableAudioContextSounds: true, sounds: { disconnect: silence, incoming: silence, outgoing: silence, }, }); device.on("ready", function (device) { device.connect({ language, voice, prompt }); }); device.on("disconnect", () => resolve()); device.on("error", () => resolve()); }); }; export const TextToSpeechButton = ({ form }) => { const { providerId, language, voice, promptText: prompt } = form.formData; const [loading, setLoading] = useState(false); const [ttsProvider, setTTSProvider] = useState< undefined | { provider: TTSProvider } >(undefined); const [playText, setPlayText] = useState< undefined | { func: () => Promise } >(undefined); useEffect(() => { (async () => { if (providerId) { setLoading(true); setTTSProvider({ provider: await tts(providerId) }); setLoading(false); } })(); }, [providerId]); useEffect(() => { (async () => { setPlayText({ func: async () => { setLoading(true); if (ttsProvider) await ttsProvider.provider(voice, language, prompt); setLoading(false); }, }); })(); }, [prompt, language, voice, ttsProvider?.provider]); const disabled = !(providerId && prompt?.length >= 2 && voice && language); /* TODO add this back to IconButtonwhen we know how to extend MUI theme and appease typescript variant="contained" */ return ( {!loading && } {loading && } ); }; export const PromptInput = (form, ...rest) => { return ( }} {...rest} /> ); }; const validateVoice = (args, values) => { if (!values.language) return "validation.language"; if (!values.voice) return "validation.voice"; const availableVoices = TwilioLanguages.voices[values.language]; const found = availableVoices.filter((v) => v.id === values.voice).length === 1; if (!found) return "validation.voice"; }; export const VoiceInput = (form, ...rest) => { const voice = TwilioLanguages.voices[form.formData.language] || []; return ( ); }; let noAvailableNumbers = false; let availableNumbers = []; const getAvailableNumbers = async (providerId) => { try { const r = await fetch(`/api/v1/voice/providers/${providerId}/freeNumbers`); availableNumbers = await r.json(); noAvailableNumbers = availableNumbers.length === 0; return availableNumbers; } catch (error) { console.error( `Could not fetch available numbers for provider ${providerId} - ${error}` ); return []; } }; const sidToNumber = (sid) => { return availableNumbers .filter(({ id }) => id === sid) .map(({ name }) => name)[0]; }; export const populateNumber = (data) => { return { ...data, number: sidToNumber(data.providerLineSid), }; }; const hasNumbers = (args, value, values, translate, ...props) => { if (noAvailableNumbers) return "validation.noAvailableNumbers"; }; export const AvailableNumbersInput = (form, ...rest) => { const { // @ts-expect-error: non-existent property meta: { touched, error } = {}, // @ts-expect-error: non-existent property input: { ...inputProps }, ...props } = rest; const translate = useTranslate(); const notify = useNotify(); const [loading, setLoading] = useState(false); const [choices, setChoices] = useState({}); // @ts-expect-error: Invalid return type useEffect(async () => { if (form && form.formData && form.formData.providerId) { setLoading(true); const choices = await getAvailableNumbers(form.formData.providerId); setChoices({ choices, helperText: noAvailableNumbers ? translate("validation.noAvailableNumbers") : "", }); if (noAvailableNumbers) notify("validation.noAvailableNumbers", "error"); setLoading(false); } }, [form && form.formData ? form.formData.providerId : undefined]); return ( <> {loading && } ); }; /* const voiceLineName = voiceLine => { return voiceLine.number } const getVoiceLineChoices = async ():Promise => { try { const r = await fetch(`/api/v1/voice/voice-line`); const lines = await r.json(); if(lines.data?.length > 0) { return lines.data.map(voiceLine => ({"id": voiceLine.id, "name": voiceLineName(voiceLine)})) } return []; } catch (error) { console.error( `Could not fetch voice lines error: ${error}` ); return []; } } export const AsyncSelectInput = (choiceLoader: () => Promise, label, source, translationEmpty,) => (form, ...rest) => { const { meta: { touched, error } = {}, input: { ...inputProps }, ...props } = rest; const translate = useTranslate(); const notify = useNotify(); const [loading, setLoading] = useState(false); const [choices, setChoices] = useState({choices: []}); useEffect(() => { (async () => { setLoading(true); //const items = await choiceLoader() const items = [{"id": "testing", "name": "OMG"}] setChoices({ choices: items, helperText: items.length === 0 ? translate(translationEmpty) : "", }); if (items.length === 0) notify(translationEmpty, "error"); setLoading(false); })()}, [form && form.formData ? form.formData.providerId : undefined]); const isNotEmpty = () => { if (choices.choices.length === 0) return translationEmpty; return undefined; }; return ( <> {choices.choices.length > 0 && } {loading && } ) } export const VoiceLineSelectInput = AsyncSelectInput(getVoiceLineChoices, "Voice Line", "backendId", "validation.noVoiceLines" ) */ export const VoiceLineSelectInput = (source: string) => () => ( ); export const VoiceLineField = (source: string) => () => ( );