diff --git a/packages/components/src/jobs/InferenceJob.ts b/packages/components/src/jobs/InferenceJob.ts index 638530ee56..93dca97161 100644 --- a/packages/components/src/jobs/InferenceJob.ts +++ b/packages/components/src/jobs/InferenceJob.ts @@ -6,6 +6,8 @@ export enum FrontendInferenceJobType { FaceAnimation, TextToSpeech, VoiceConversion, + VoiceDesignerCreateVoice, + VoiceDesignerTts, } // NB: Many of these fields are optional despite the response payload containing them @@ -35,11 +37,11 @@ export class InferenceJob { constructor( // PK - jobToken: string, + jobToken: string, // Frontend state frontendJobType: FrontendInferenceJobType, // Status - status: string = 'unknown', + status: string = "unknown", maybeExtraStatusDescription: string | null = null, attemptCount: number = 0, // Request @@ -85,7 +87,10 @@ export class InferenceJob { } } - static fromResponse(response: ModelInferenceJobStatus, frontendJobType: FrontendInferenceJobType) : InferenceJob { + static fromResponse( + response: ModelInferenceJobStatus, + frontendJobType: FrontendInferenceJobType + ): InferenceJob { return new InferenceJob( response.job_token, frontendJobType, diff --git a/packages/fakeyou.com/src/App.tsx b/packages/fakeyou.com/src/App.tsx index b9ebb74f97..83502a29fb 100644 --- a/packages/fakeyou.com/src/App.tsx +++ b/packages/fakeyou.com/src/App.tsx @@ -3,7 +3,7 @@ import "scss/custom-bootstrap.scss"; import React from "react"; import { ApiConfig } from "@storyteller/components"; -import Cookies from 'universal-cookie'; +import Cookies from "universal-cookie"; import { DetectLocale, DetectLocaleIsOk, @@ -60,14 +60,17 @@ import { VoiceConversionModelUploadJob } from "@storyteller/components/src/jobs/ import { VoiceConversionModelListItem } from "@storyteller/components/src/api/voice_conversion/ListVoiceConversionModels"; import HttpBackend from "i18next-http-backend"; -import { InferenceJobs, Session as SessionProvider } from "components/providers"; +import { + InferenceJobs, + Session as SessionProvider, +} from "components/providers"; // NB: We're transitioning over to this instance of i18n-next that loads translations over HTTP from Json Files. // The old i18n-next instance (see below) bakes in translations into the compiled javascript blob. // This new instance uses the Locize paid service to manage translation strings on their website. It's automated, // can easily sync to version control, and makes translation easy to maintain across a wide number of languages. export const i18n2 = i18n.createInstance(); -declare const window: Window & { dataLayer: Record[]; }; +declare const window: Window & { dataLayer: Record[] }; // OLD i18n-next instance // This instance of i18n-next should not be used for new translations going forward. @@ -196,6 +199,11 @@ function initInferenceJobsByCategoryMap(): Map< inferenceJobsByCategory.set(FrontendInferenceJobType.FaceAnimation, []); inferenceJobsByCategory.set(FrontendInferenceJobType.TextToSpeech, []); inferenceJobsByCategory.set(FrontendInferenceJobType.VoiceConversion, []); + inferenceJobsByCategory.set( + FrontendInferenceJobType.VoiceDesignerCreateVoice, + [] + ); + inferenceJobsByCategory.set(FrontendInferenceJobType.VoiceDesignerTts, []); return inferenceJobsByCategory; } @@ -258,7 +266,6 @@ class App extends React.Component { voiceConversionModels: [], maybeSelectedVoiceConversionModel: undefined, }; - } componentWillMount() { @@ -302,14 +309,17 @@ class App extends React.Component { // Track only logged-in users (for now) PosthogClient.enablePosthog(); PosthogClient.setUsername(username); - cookies.set('logged_in_username', username, { path: '/', expires: new Date(Date.now()+3 * 86400000) }); + cookies.set("logged_in_username", username, { + path: "/", + expires: new Date(Date.now() + 3 * 86400000), + }); window.dataLayer.push({ - 'user_id': username, + user_id: username, }); } else { - cookies.remove('logged_in_username', { path: '/' }); + cookies.remove("logged_in_username", { path: "/" }); } - + if (!deepEqual(sessionWrapper, this.state.sessionWrapper)) { this.setState({ sessionWrapper: sessionWrapper, @@ -321,16 +331,24 @@ class App extends React.Component { const cookies = new Cookies(); const sessionSubscriptionsWrapper = - await SessionSubscriptionsWrapper.lookupActiveSubscriptions(); + await SessionSubscriptionsWrapper.lookupActiveSubscriptions(); const plan = sessionSubscriptionsWrapper.getActiveProductSlug(); if (plan !== undefined) { - cookies.set('logged_in_user_plan', plan, { path: '/', expires: new Date(Date.now()+3 * 86400000) }); + cookies.set("logged_in_user_plan", plan, { + path: "/", + expires: new Date(Date.now() + 3 * 86400000), + }); } else { - cookies.remove('logged_in_user_plan', { path: '/' }); + cookies.remove("logged_in_user_plan", { path: "/" }); } - if (!deepEqual(sessionSubscriptionsWrapper, this.state.sessionSubscriptionsWrapper)) { + if ( + !deepEqual( + sessionSubscriptionsWrapper, + this.state.sessionSubscriptionsWrapper + ) + ) { this.setState({ sessionSubscriptionsWrapper: sessionSubscriptionsWrapper, }); @@ -812,109 +830,123 @@ class App extends React.Component { */}
- - - - - - - - - + + + + + + + + +
diff --git a/packages/fakeyou.com/src/v2/view/PageContainer.tsx b/packages/fakeyou.com/src/v2/view/PageContainer.tsx index 8b41205b6f..23de182521 100644 --- a/packages/fakeyou.com/src/v2/view/PageContainer.tsx +++ b/packages/fakeyou.com/src/v2/view/PageContainer.tsx @@ -678,6 +678,8 @@ class PageContainer extends React.Component< this.props.sessionSubscriptionsWrapper } inferenceJobs={this.props.inferenceJobs} + enqueueInferenceJob={this.props.enqueueInferenceJob} + inferenceJobsByCategory={this.props.inferenceJobsByCategory} ttsInferenceJobs={this.props.ttsInferenceJobs} /> diff --git a/packages/fakeyou.com/src/v2/view/pages/voice_designer/VoiceDesignerFormPage.tsx b/packages/fakeyou.com/src/v2/view/pages/voice_designer/VoiceDesignerFormPage.tsx index 843e8dadff..965417535d 100644 --- a/packages/fakeyou.com/src/v2/view/pages/voice_designer/VoiceDesignerFormPage.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/voice_designer/VoiceDesignerFormPage.tsx @@ -1,6 +1,11 @@ import React, { useState } from "react"; import { useParams } from "react-router-dom"; -import { faEye, faLanguage, faPencil, faWaveform } from "@fortawesome/pro-solid-svg-icons"; +import { + faEye, + faLanguage, + faPencil, + faWaveform, +} from "@fortawesome/pro-solid-svg-icons"; import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle"; import Panel from "components/common/Panel"; import { Stepper } from "./components/Stepper"; @@ -24,8 +29,8 @@ interface RouteParams { function VoiceDesignerFormPage() { const history = useHistory(); const { datasets, inputCtrl, voices } = useVoiceRequests(); - const [language,languageSet] = useState("en"); - const [visibility,visibilitySet] = useState(""); + const [language, languageSet] = useState("en"); + const [visibility, visibilitySet] = useState(""); const [title, titleSet] = useState(""); const audioProps = useFile({}); // contains upload inout state and controls, see docs @@ -36,12 +41,35 @@ function VoiceDesignerFormPage() { { value: "fr", label: "French" }, ]; - const visibilityOptions = [{ label: "Public", value: "public" },{ label: "Hidden", value: "hidden" }]; + const visibilityOptions = [ + { label: "Public", value: "public" }, + { label: "Hidden", value: "hidden" }, + ]; const datasetInputs = [ - { type: "text", label: "Title", placeholder: "Voice name", value: title, onChange: inputCtrl(titleSet) }, - { type: "select", icon: faLanguage, label: "Language", value: language, onChange: inputCtrl(languageSet), options: languages }, - { type: "select", icon: faEye, label: "Visibility", value: visibility, onChange: inputCtrl(visibilitySet), options: visibilityOptions } + { + type: "text", + label: "Title", + placeholder: "Voice name", + value: title, + onChange: inputCtrl(titleSet), + }, + { + type: "select", + icon: faLanguage, + label: "Language", + value: language, + onChange: inputCtrl(languageSet), + options: languages, + }, + { + type: "select", + icon: faEye, + label: "Visibility", + value: visibility, + onChange: inputCtrl(visibilitySet), + options: visibilityOptions, + }, ]; const { dataset_token } = useParams(); @@ -62,7 +90,9 @@ function VoiceDesignerFormPage() { case 0: return ; case 1: - return ; + return ( + + ); default: return null; } @@ -83,17 +113,17 @@ function VoiceDesignerFormPage() { if (isNewCreation) { // It's a new creation and on the first step - datasets.create("",{ - title, - creator_set_visibility: visibility, - idempotency_token: uuidv4(), - }).then((res: any) => { - - if (res && res.success && res.token) { - history.push(`/voice-designer/dataset/${ res.token }/upload`); - } - }); - + datasets + .create("", { + title, + creator_set_visibility: visibility, + idempotency_token: uuidv4(), + }) + .then((res: any) => { + if (res && res.success && res.token) { + history.push(`/voice-designer/dataset/${res.token}/upload`); + } + }); } else if (dataset_token) { // It's edit mode and on the first step history.push(`/voice-designer/dataset/${dataset_token}/upload`); @@ -105,14 +135,16 @@ function VoiceDesignerFormPage() { }; const handleCreateVoice = () => { - voices.create("",{ - uuid_idempotency_token: uuidv4(), - voice_dataset_token: dataset_token || "", - }).then((res: any) => { - if (res && res.success) { - history.push("/voice-designer"); - } - }); + voices + .create("", { + uuid_idempotency_token: uuidv4(), + voice_dataset_token: dataset_token || "", + }) + .then((res: any) => { + if (res && res.success) { + history.push("/voice-designer"); + } + }); }; return ( diff --git a/packages/fakeyou.com/src/v2/view/pages/voice_designer/VoiceDesignerUseVoicePage.tsx b/packages/fakeyou.com/src/v2/view/pages/voice_designer/VoiceDesignerUseVoicePage.tsx index 5584415953..137a9fa253 100644 --- a/packages/fakeyou.com/src/v2/view/pages/voice_designer/VoiceDesignerUseVoicePage.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/voice_designer/VoiceDesignerUseVoicePage.tsx @@ -20,18 +20,28 @@ import Container from "components/common/Container/Container"; import TextArea from "components/common/TextArea"; import { Button } from "components/common"; import { SessionTtsInferenceResultList } from "v2/view/_common/SessionTtsInferenceResultsList"; -import { InferenceJob } from "@storyteller/components/src/jobs/InferenceJob"; +import { + FrontendInferenceJobType, + InferenceJob, +} from "@storyteller/components/src/jobs/InferenceJob"; import { TtsInferenceJob } from "@storyteller/components/src/jobs/TtsInferenceJobs"; import { faVolumeUp } from "@fortawesome/free-solid-svg-icons"; import { useParams } from "react-router-dom"; import { GetVoice } from "@storyteller/components/src/api/voice_designer/voices/GetVoice"; import Skeleton from "components/common/Skeleton"; +import useVoiceRequests from "./useVoiceRequests"; +import { v4 as uuidv4 } from "uuid"; interface VoiceDesignerUseVoicePageProps { sessionWrapper: SessionWrapper; sessionSubscriptionsWrapper: SessionSubscriptionsWrapper; inferenceJobs: Array; ttsInferenceJobs: Array; + enqueueInferenceJob: ( + jobToken: string, + frontendInferenceJobType: FrontendInferenceJobType + ) => void; + inferenceJobsByCategory: Map>; } export default function VoiceDesignerUseVoicePage( @@ -50,6 +60,8 @@ export default function VoiceDesignerUseVoicePage( voiceToken: "", languageTag: "", }); + const { inference } = useVoiceRequests(); + const [isEnqueuing, setIsEnqueuing] = useState(false); const getVoiceDetails = useCallback(async (voice_token) => { try { @@ -107,8 +119,28 @@ export default function VoiceDesignerUseVoicePage( // { label: "Front page featured?", value: frontPageFeatured }, // ]; - const handleFormSubmit = async (ev: React.FormEvent) => { - ev.preventDefault(); + const handleEnqueueTts = () => { + setIsEnqueuing(true); + inference + .enqueue("", { + uuid_idempotency_token: uuidv4(), + text: textBuffer, + voice_token: voice_token, + }) + .then((res: any) => { + if (res && res.success) { + props.enqueueInferenceJob( + res.inference_job_token, + FrontendInferenceJobType.VoiceDesignerTts + ); + } + }) + .catch((error) => { + console.error("Error enqueuing TTS:", error); + }) + .finally(() => { + setIsEnqueuing(false); + }); }; const handleChangeText = (ev: React.FormEvent) => { @@ -158,7 +190,7 @@ export default function VoiceDesignerUseVoicePage( -
+

@@ -172,7 +204,13 @@ export default function VoiceDesignerUseVoicePage( rows={8} />
-