enqueue voice designer tts

This commit is contained in:
BFlat committed 2023-11-16 15:53:05 +07:00
1 parent b4ef0f80ee
commit 6bc63a317d
6 files changed
+268 -152

No files matched your search

+8 -3
View File
@@ -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,
+147 -115
View File
@@ -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<string, unknown>[]; };
declare const window: Window & { dataLayer: Record<string, unknown>[] };
// 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<Props, State> {
voiceConversionModels: [],
maybeSelectedVoiceConversionModel: undefined,
};
}
componentWillMount() {
@@ -302,14 +309,17 @@ class App extends React.Component<Props, State> {
// 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<Props, State> {
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<Props, State> {
*/}
<div className="migrationComponentWrapper">
<InferenceJobs {...{
byCategory: this.state.inferenceJobsByCategory
}}>
<SessionProvider {...{ sessionWrapper: this.state.sessionWrapper }}>
<Switch>
<Route path="/">
<PageContainer
sessionWrapper={this.state.sessionWrapper}
querySessionAction={this.querySession}
sessionSubscriptionsWrapper={
this.state.sessionSubscriptionsWrapper
}
querySessionSubscriptionsAction={
this.querySessionSubscriptions
}
isShowingVocodesNotice={this.state.isShowingVocodesNotice}
clearVocodesNotice={this.clearVocodesNotice}
isShowingLangaugeNotice={this.state.isShowingLanguageNotice}
clearLanguageNotice={this.clearLanguageNotice}
displayLanguage={this.state.displayLanguage}
primaryLanguageCode={this.state.primaryLanguageCode}
isShowingTwitchTtsNotice={
this.state.isShowingTwitchTtsNotice
}
clearTwitchTtsNotice={this.clearTwitchTtsNotice}
isShowingPleaseFollowNotice={
this.state.isShowingPleaseFollowNotice
}
clearPleaseFollowNotice={this.clearPleaseFollowNotice}
isShowingBootstrapLanguageNotice={
this.state.isShowingBootstrapLanguageNotice
}
clearBootstrapLanguageNotice={
this.clearBootstrapLanguageNotice
}
enqueueInferenceJob={this.enqueueInferenceJob}
inferenceJobs={this.state.inferenceJobs}
inferenceJobsByCategory={this.state.inferenceJobsByCategory}
enqueueTtsJob={this.enqueueTtsJob}
ttsInferenceJobs={this.state.ttsInferenceJobs}
enqueueW2lJob={this.enqueueW2lJob}
w2lInferenceJobs={this.state.w2lInferenceJobs}
enqueueTtsModelUploadJob={this.enqueueTtsModelUploadJob}
ttsModelUploadJobs={this.state.ttsModelUploadJobs}
enqueueW2lTemplateUploadJob={
this.enqueueW2lTemplateUploadJob
}
w2lTemplateUploadJobs={this.state.w2lTemplateUploadJobs}
enqueueVocoderUploadJob={this.enqueueVocoderUploadJob}
vocoderUploadJobs={this.state.vocoderUploadJobs}
enqueueVoiceConversionModelUploadJob={
this.enqueueVoiceConversionModelUploadJob
}
voiceConversionModelUploadJobs={
this.state.voiceConversionModelUploadJobs
}
textBuffer={this.state.textBuffer}
setTextBuffer={this.setTextBuffer}
clearTextBuffer={this.clearTextBuffer}
ttsModels={this.props.allTtsModels}
setTtsModels={this.props.setAllTtsModels}
allTtsCategories={this.props.allTtsCategories}
setAllTtsCategories={this.props.setAllTtsCategories}
computedTtsCategoryAssignments={
this.props.computedTtsCategoryAssignments
}
setComputedTtsCategoryAssignments={
this.props.setComputedTtsCategoryAssignments
}
allTtsCategoriesByTokenMap={
this.props.allTtsCategoriesByTokenMap
}
allTtsModelsByTokenMap={this.props.allTtsModelsByTokenMap}
ttsModelsByCategoryToken={
this.props.ttsModelsByCategoryToken
}
dropdownCategories={this.props.dropdownCategories}
setDropdownCategories={this.props.setDropdownCategories}
selectedCategories={this.props.selectedCategories}
setSelectedCategories={this.props.setSelectedCategories}
maybeSelectedTtsModel={this.props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={
this.props.setMaybeSelectedTtsModel
}
selectedTtsLanguageScope={
this.props.selectedTtsLanguageScope
}
setSelectedTtsLanguageScope={
this.props.setSelectedTtsLanguageScope
}
voiceConversionModels={this.state.voiceConversionModels}
setVoiceConversionModels={this.setAllVoiceConversionModels}
maybeSelectedVoiceConversionModel={
this.state.maybeSelectedVoiceConversionModel
}
setMaybeSelectedVoiceConversionModel={
this.setMaybeSelectedVoiceConversionModel
}
/>
</Route>
</Switch>
</SessionProvider>
</InferenceJobs>
<InferenceJobs
{...{
byCategory: this.state.inferenceJobsByCategory,
}}
>
<SessionProvider
{...{ sessionWrapper: this.state.sessionWrapper }}
>
<Switch>
<Route path="/">
<PageContainer
sessionWrapper={this.state.sessionWrapper}
querySessionAction={this.querySession}
sessionSubscriptionsWrapper={
this.state.sessionSubscriptionsWrapper
}
querySessionSubscriptionsAction={
this.querySessionSubscriptions
}
isShowingVocodesNotice={
this.state.isShowingVocodesNotice
}
clearVocodesNotice={this.clearVocodesNotice}
isShowingLangaugeNotice={
this.state.isShowingLanguageNotice
}
clearLanguageNotice={this.clearLanguageNotice}
displayLanguage={this.state.displayLanguage}
primaryLanguageCode={this.state.primaryLanguageCode}
isShowingTwitchTtsNotice={
this.state.isShowingTwitchTtsNotice
}
clearTwitchTtsNotice={this.clearTwitchTtsNotice}
isShowingPleaseFollowNotice={
this.state.isShowingPleaseFollowNotice
}
clearPleaseFollowNotice={this.clearPleaseFollowNotice}
isShowingBootstrapLanguageNotice={
this.state.isShowingBootstrapLanguageNotice
}
clearBootstrapLanguageNotice={
this.clearBootstrapLanguageNotice
}
enqueueInferenceJob={this.enqueueInferenceJob}
inferenceJobs={this.state.inferenceJobs}
inferenceJobsByCategory={
this.state.inferenceJobsByCategory
}
enqueueTtsJob={this.enqueueTtsJob}
ttsInferenceJobs={this.state.ttsInferenceJobs}
enqueueW2lJob={this.enqueueW2lJob}
w2lInferenceJobs={this.state.w2lInferenceJobs}
enqueueTtsModelUploadJob={this.enqueueTtsModelUploadJob}
ttsModelUploadJobs={this.state.ttsModelUploadJobs}
enqueueW2lTemplateUploadJob={
this.enqueueW2lTemplateUploadJob
}
w2lTemplateUploadJobs={this.state.w2lTemplateUploadJobs}
enqueueVocoderUploadJob={this.enqueueVocoderUploadJob}
vocoderUploadJobs={this.state.vocoderUploadJobs}
enqueueVoiceConversionModelUploadJob={
this.enqueueVoiceConversionModelUploadJob
}
voiceConversionModelUploadJobs={
this.state.voiceConversionModelUploadJobs
}
textBuffer={this.state.textBuffer}
setTextBuffer={this.setTextBuffer}
clearTextBuffer={this.clearTextBuffer}
ttsModels={this.props.allTtsModels}
setTtsModels={this.props.setAllTtsModels}
allTtsCategories={this.props.allTtsCategories}
setAllTtsCategories={this.props.setAllTtsCategories}
computedTtsCategoryAssignments={
this.props.computedTtsCategoryAssignments
}
setComputedTtsCategoryAssignments={
this.props.setComputedTtsCategoryAssignments
}
allTtsCategoriesByTokenMap={
this.props.allTtsCategoriesByTokenMap
}
allTtsModelsByTokenMap={
this.props.allTtsModelsByTokenMap
}
ttsModelsByCategoryToken={
this.props.ttsModelsByCategoryToken
}
dropdownCategories={this.props.dropdownCategories}
setDropdownCategories={this.props.setDropdownCategories}
selectedCategories={this.props.selectedCategories}
setSelectedCategories={this.props.setSelectedCategories}
maybeSelectedTtsModel={this.props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={
this.props.setMaybeSelectedTtsModel
}
selectedTtsLanguageScope={
this.props.selectedTtsLanguageScope
}
setSelectedTtsLanguageScope={
this.props.setSelectedTtsLanguageScope
}
voiceConversionModels={this.state.voiceConversionModels}
setVoiceConversionModels={
this.setAllVoiceConversionModels
}
maybeSelectedVoiceConversionModel={
this.state.maybeSelectedVoiceConversionModel
}
setMaybeSelectedVoiceConversionModel={
this.setMaybeSelectedVoiceConversionModel
}
/>
</Route>
</Switch>
</SessionProvider>
</InferenceJobs>
</div>
</div>
</div>
@@ -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}
/>
</Route>
@@ -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<RouteParams>();
@@ -62,7 +90,9 @@ function VoiceDesignerFormPage() {
case 0:
return <VoiceDetails {...{ datasetInputs }} />;
case 1:
return <UploadSamples {...{ audioProps, datasetToken: dataset_token }}/>;
return (
<UploadSamples {...{ audioProps, datasetToken: dataset_token }} />
);
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 (
@@ -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<InferenceJob>;
ttsInferenceJobs: Array<TtsInferenceJob>;
enqueueInferenceJob: (
jobToken: string,
frontendInferenceJobType: FrontendInferenceJobType
) => void;
inferenceJobsByCategory: Map<FrontendInferenceJobType, Array<InferenceJob>>;
}
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<HTMLFormElement>) => {
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<HTMLTextAreaElement>) => {
@@ -158,7 +190,7 @@ export default function VoiceDesignerUseVoicePage(
<PageHeader title={voiceData.title} subText={subText} />
<Panel padding={true} mb={true}>
<form onSubmit={handleFormSubmit}>
<form>
<div className="row g-4">
<div className="col-12 col-lg-6 d-flex flex-column gap-3">
<h4>
@@ -172,7 +204,13 @@ export default function VoiceDesignerUseVoicePage(
rows={8}
/>
<div className="d-flex gap-3">
<Button icon={faVolumeUp} label="Speak" full={true} />
<Button
icon={faVolumeUp}
label="Speak"
full={true}
onClick={handleEnqueueTts}
isLoading={isEnqueuing}
/>
<Button
icon={faDeleteLeft}
label="Clear"
@@ -188,7 +226,11 @@ export default function VoiceDesignerUseVoicePage(
</h4>
<div className="d-flex flex-column gap-3 session-tts-section">
<SessionTtsInferenceResultList
inferenceJobs={props.inferenceJobs}
inferenceJobs={
props.inferenceJobsByCategory.get(
FrontendInferenceJobType.VoiceDesignerTts
)!
}
ttsInferenceJobs={props.ttsInferenceJobs}
sessionSubscriptionsWrapper={
props.sessionSubscriptionsWrapper
@@ -21,6 +21,7 @@ import {
UpdateDataset,
UpdateDatasetRequest,
} from "@storyteller/components/src/api/voice_designer/voice_datasets/UpdateDataset";
import { EnqueueTts } from "@storyteller/components/src/api/voice_designer/inference/EnqueueTts";
import { useSession } from "hooks";
export default function useVoiceRequests() {
@@ -84,7 +85,9 @@ export default function useVoiceRequests() {
list: datasets,
byToken: datasetByToken,
},
inference: {},
inference: {
enqueue: EnqueueTts,
},
voices: {
create: CreateVoice,
delete: deleteVoice,