trump page

This commit is contained in:
BFlat authored and Brandon Thomas committed 2023-04-01 01:43:25 -07:00
1 parent ff9dc8f49e
commit 7c8cfab5f4
4 files changed
+586

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

@@ -66,6 +66,7 @@ import { GetComputedTtsCategoryAssignmentsSuccessResponse } from "@storyteller/c
import { NewsPage } from "./pages/news/NewsPage";
import { LandingPage } from "./pages/landing/LandingPage";
import { ChannelsPage } from "./pages/channels/Channels";
import { TrumpTtsPage } from "./pages/trump/TrumpTtsPage";
//import { LandingPage } from "./pages/landing/LandingPage";
//import { VcModelListPage } from "./pages/vc/vc_model_list/VcModelListPage";
@@ -479,6 +480,48 @@ class PageContainer extends React.Component<Props, State> {
<VocodesPage />
</Route>
<Route path="/donald-trump">
<TrumpTtsPage
sessionWrapper={this.props.sessionWrapper}
sessionSubscriptionsWrapper={
this.props.sessionSubscriptionsWrapper
}
enqueueTtsJob={this.props.enqueueTtsJob}
ttsInferenceJobs={this.props.ttsInferenceJobs}
ttsModelUploadJobs={this.props.ttsModelUploadJobs}
w2lInferenceJobs={this.props.w2lInferenceJobs}
w2lTemplateUploadJobs={this.props.w2lTemplateUploadJobs}
textBuffer={this.props.textBuffer}
setTextBuffer={this.props.setTextBuffer}
clearTextBuffer={this.props.clearTextBuffer}
ttsModels={this.props.ttsModels}
setTtsModels={this.props.setTtsModels}
allTtsCategories={this.props.allTtsCategories}
setAllTtsCategories={this.props.setAllTtsCategories}
allTtsCategoriesByTokenMap={
this.props.allTtsCategoriesByTokenMap
}
computedTtsCategoryAssignments={
this.props.computedTtsCategoryAssignments
}
setComputedTtsCategoryAssignments={
this.props.setComputedTtsCategoryAssignments
}
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
}
/>
</Route>
<Route path="/">
<TtsModelListPage
sessionWrapper={this.props.sessionWrapper}
@@ -0,0 +1,462 @@
import React, { useEffect, useCallback, useState } from "react";
import { t } from "i18next";
import { Trans } from "react-i18next";
import { Link } from "react-router-dom";
import { SessionTtsInferenceResultList } from "../../_common/SessionTtsInferenceResultsList";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { TtsInferenceJob } from "@storyteller/components/src/jobs/TtsInferenceJobs";
import { TtsModelUploadJob } from "@storyteller/components/src/jobs/TtsModelUploadJobs";
import { W2lInferenceJob } from "@storyteller/components/src/jobs/W2lInferenceJobs";
import { W2lTemplateUploadJob } from "@storyteller/components/src/jobs/W2lTemplateUploadJobs";
import { v4 as uuidv4 } from "uuid";
import {
ListTtsModels,
TtsModelListItem,
} from "@storyteller/components/src/api/tts/ListTtsModels";
import {
GenerateTtsAudio,
GenerateTtsAudioErrorType,
GenerateTtsAudioIsError,
GenerateTtsAudioIsOk,
} from "@storyteller/components/src/api/tts/GenerateTtsAudio";
import {
ListTtsCategories,
ListTtsCategoriesIsError,
ListTtsCategoriesIsOk,
} from "@storyteller/components/src/api/category/ListTtsCategories";
import { TtsCategoryType } from "../../../../AppWrapper";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faBarsStaggered,
faDeleteLeft,
faVolumeHigh,
} from "@fortawesome/free-solid-svg-icons";
import { motion } from "framer-motion";
import { container, panel } from "../../../../data/animation";
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
import { TtsPageHero } from "./TrumpTtsPageHero";
import { Analytics } from "../../../../common/Analytics";
import {
GetComputedTtsCategoryAssignments,
GetComputedTtsCategoryAssignmentsIsError,
GetComputedTtsCategoryAssignmentsIsOk,
GetComputedTtsCategoryAssignmentsSuccessResponse,
} from "@storyteller/components/src/api/category/GetComputedTtsCategoryAssignments";
import { DynamicallyCategorizeModels } from "../../../../model/categories/SyntheticCategory";
import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle";
export interface EnqueueJobResponsePayload {
success: boolean;
inference_job_token?: string;
}
interface Props {
sessionWrapper: SessionWrapper;
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
enqueueTtsJob: (jobToken: string) => void;
ttsInferenceJobs: Array<TtsInferenceJob>;
ttsModelUploadJobs: Array<TtsModelUploadJob>;
w2lInferenceJobs: Array<W2lInferenceJob>;
w2lTemplateUploadJobs: Array<W2lTemplateUploadJob>;
textBuffer: string;
setTextBuffer: (textBuffer: string) => void;
clearTextBuffer: () => void;
ttsModels: Array<TtsModelListItem>;
setTtsModels: (ttsVoices: Array<TtsModelListItem>) => void;
allTtsCategories: TtsCategoryType[];
setAllTtsCategories: (allTtsCategories: TtsCategoryType[]) => void;
computedTtsCategoryAssignments?: GetComputedTtsCategoryAssignmentsSuccessResponse;
setComputedTtsCategoryAssignments: (
categoryAssignments: GetComputedTtsCategoryAssignmentsSuccessResponse
) => void;
allTtsCategoriesByTokenMap: Map<string, TtsCategoryType>;
allTtsModelsByTokenMap: Map<string, TtsModelListItem>;
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>;
dropdownCategories: TtsCategoryType[][];
setDropdownCategories: (dropdownCategories: TtsCategoryType[][]) => void;
selectedCategories: TtsCategoryType[];
setSelectedCategories: (selectedCategories: TtsCategoryType[]) => void;
maybeSelectedTtsModel?: TtsModelListItem;
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void;
selectedTtsLanguageScope: string;
setSelectedTtsLanguageScope: (selectedTtsLanguageScope: string) => void;
}
function TrumpTtsPage(props: Props) {
//Loading spinning icon
const [loading, setLoading] = useState(false);
const [isAudioLimitAlertVisible, setAudioLimitAlertVisible] = useState(false);
const handleLoading = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
}, 2000);
};
useEffect(() => {
const timeout = setTimeout(() => {
setLoading(false);
}, 2000);
return () => clearTimeout(timeout);
}, []);
let {
setTtsModels,
setAllTtsCategories,
setComputedTtsCategoryAssignments,
ttsModels,
allTtsCategories,
computedTtsCategoryAssignments,
maybeSelectedTtsModel,
setMaybeSelectedTtsModel,
} = props;
const [maybeTtsError, setMaybeTtsError] = useState<
GenerateTtsAudioErrorType | undefined
>(undefined);
const ttsModelsLoaded = ttsModels.length > 0;
const ttsCategoriesLoaded = allTtsCategories.length > 0;
const computedTtsCategoryAssignmentsLoaded =
computedTtsCategoryAssignments !== undefined &&
computedTtsCategoryAssignments.category_token_to_tts_model_tokens.recursive
.size > 0;
const listModels = useCallback(async () => {
if (ttsModelsLoaded) {
return; // Already queried.
}
const models = await ListTtsModels();
if (models) {
DynamicallyCategorizeModels(models);
setTtsModels(models);
if (!maybeSelectedTtsModel && models.length > 0) {
let model = models[0];
const featuredModels = models.filter((m) => m.is_front_page_featured);
if (featuredModels.length > 0) {
// Random featured model
model =
featuredModels[Math.floor(Math.random() * featuredModels.length)];
}
setMaybeSelectedTtsModel(model);
}
}
}, [
setTtsModels,
maybeSelectedTtsModel,
setMaybeSelectedTtsModel,
ttsModelsLoaded,
]);
const listTtsCategories = useCallback(async () => {
if (ttsCategoriesLoaded) {
return; // Already queried.
}
const categoryList = await ListTtsCategories();
if (ListTtsCategoriesIsOk(categoryList)) {
let categories = categoryList.categories;
// NB: We'll use the frontend to order the synthetic categories first.
const LATEST_MODELS_CATEGORY_TOKEN = "SYNTHETIC_CATEGORY:LATEST_MODELS";
const TRENDING_MODELS_CATEGORY_TOKEN =
"SYNTHETIC_CATEGORY:TRENDING_MODELS";
let maybeLatestCategory = categories.find(
(category) => category.category_token === LATEST_MODELS_CATEGORY_TOKEN
);
let maybeTrendingCategory = categories.find(
(category) => category.category_token === TRENDING_MODELS_CATEGORY_TOKEN
);
let otherCategories = categories
.filter(
(category) => category.category_token !== LATEST_MODELS_CATEGORY_TOKEN
)
.filter(
(category) =>
category.category_token !== TRENDING_MODELS_CATEGORY_TOKEN
);
categories = [];
if (maybeLatestCategory !== undefined) {
categories.push(maybeLatestCategory);
}
if (maybeTrendingCategory !== undefined) {
categories.push(maybeTrendingCategory);
}
categories.push(...otherCategories);
setAllTtsCategories(categories);
} else if (ListTtsCategoriesIsError(categoryList)) {
// TODO: Retry on decay function
}
}, [setAllTtsCategories, ttsCategoriesLoaded]);
const getComputedAssignments = useCallback(async () => {
if (computedTtsCategoryAssignmentsLoaded) {
return; // Already queried.
}
const computedAssignments = await GetComputedTtsCategoryAssignments();
if (GetComputedTtsCategoryAssignmentsIsOk(computedAssignments)) {
setComputedTtsCategoryAssignments(computedAssignments);
} else if (GetComputedTtsCategoryAssignmentsIsError(computedAssignments)) {
// TODO: Retry on decay function
}
}, [setComputedTtsCategoryAssignments, computedTtsCategoryAssignmentsLoaded]);
useEffect(() => {
listModels();
listTtsCategories();
getComputedAssignments();
}, [listModels, listTtsCategories, getComputedAssignments]);
const handleChangeText = (ev: React.FormEvent<HTMLTextAreaElement>) => {
const textValue = (ev.target as HTMLTextAreaElement).value;
props.setTextBuffer(textValue);
setAudioLimitAlertVisible(textValue.length > 100);
};
const handleFormSubmit = async (ev: React.FormEvent<HTMLFormElement>) => {
ev.preventDefault();
if (!props.maybeSelectedTtsModel) {
return false;
}
if (!props.textBuffer) {
return false;
}
const modelToken = props.maybeSelectedTtsModel!.model_token;
const request = {
uuid_idempotency_token: uuidv4(),
tts_model_token: modelToken,
inference_text: props.textBuffer,
};
const response = await GenerateTtsAudio(request);
Analytics.ttsGenerate(modelToken, props.textBuffer.length);
if (GenerateTtsAudioIsOk(response)) {
setMaybeTtsError(undefined);
props.enqueueTtsJob(response.inference_job_token);
} else if (GenerateTtsAudioIsError(response)) {
setMaybeTtsError(response.error);
}
return false;
};
const handleClearClick = (ev: React.FormEvent<HTMLButtonElement>) => {
ev.preventDefault();
props.clearTextBuffer();
Analytics.ttsClear(props.maybeSelectedTtsModel?.model_token);
return false;
};
usePrefixedDocumentTitle(undefined); // NB: Sets to default title
// Show errors on TTS failure
let maybeError = <></>;
if (!!maybeTtsError) {
let hasMessage = false;
let message = <></>;
switch (maybeTtsError) {
case GenerateTtsAudioErrorType.TooManyRequests:
hasMessage = true;
message = (
<Trans i18nKey="pages.ttsList.errorTooManyRequests">
<strong>You're sending too many requests!</strong>
Slow down a little. We have to slow things down a little when the
server gets busy.
</Trans>
);
break;
case GenerateTtsAudioErrorType.ServerError |
GenerateTtsAudioErrorType.BadRequest |
GenerateTtsAudioErrorType.NotFound:
break;
}
if (hasMessage) {
maybeError = (
<div
className="alert alert-primary alert-dismissible fade show m-0"
role="alert"
>
<button
className="btn-close"
onClick={() => setMaybeTtsError(undefined)}
data-bs-dismiss="alert"
aria-label="Close"
></button>
{message}
</div>
);
}
}
// NB: If the text is too long, don't allow submission
let remainingCharactersButtonDisabled = props.textBuffer.trim().length > 1024;
let noTextInputButtonDisabled = props.textBuffer.trim() === "";
const speakButtonClass = loading
? "btn btn-primary w-100 disabled"
: "btn btn-primary w-100";
let audioLimitAlert = <></>;
if (
isAudioLimitAlertVisible &&
!props.sessionSubscriptionsWrapper.hasPaidFeatures()
) {
audioLimitAlert = (
<>
<div className="alert alert-warning fs-7 mb-0">
<span className="fw-semibold">
<u>Note:</u> Non-premium is limited to 12 seconds of audio.{" "}
<Link className="fw-semibold" to="/pricing">
Upgrade now
</Link>
.
</span>
</div>
</>
);
}
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<TtsPageHero
sessionWrapper={props.sessionWrapper}
sessionSubscriptionsWrapper={props.sessionSubscriptionsWrapper}
/>
<motion.div className="container-panel pb-5 mb-4" variants={panel}>
<div className="panel p-3 py-4 p-md-4">
<i className="fas fa-volume-high"></i>
<div className="d-flex gap-4">
<form
className="w-100 d-flex flex-column"
onSubmit={handleFormSubmit}
>
{/* Explore Rollout */}
{/*
EXPLORE OMNIBAR GOES HERE TODO
*/}
<div className="row gx-5 gy-5">
<div className="col-12 col-lg-6 d-flex flex-column gap-3">
<div className="d-flex flex-column gap-3 h-100">
<div className="d-flex gap-2">
<label className="sub-title pb-0">
{t("tts.TtsModelListPage.form.yourTextLabel")}
</label>
</div>
<textarea
onClick={() => {
Analytics.ttsClickTextInputBox();
}}
onChange={handleChangeText}
className="form-control text-message h-100"
value={props.textBuffer}
placeholder={t(
"tts.TtsModelListPage.form.textInputHint",
{
voice:
props.maybeSelectedTtsModel?.title || "the voice",
}
)}
></textarea>
{audioLimitAlert}
<div className="d-flex gap-3">
<button
className={speakButtonClass}
disabled={
remainingCharactersButtonDisabled ||
noTextInputButtonDisabled
}
onClick={handleLoading}
type="submit"
>
<FontAwesomeIcon icon={faVolumeHigh} className="me-2" />
{t("tts.TtsModelListPage.form.buttons.speak")}
{loading && <LoadingIcon />}
</button>
<button
className="btn btn-destructive w-100"
onClick={handleClearClick}
disabled={noTextInputButtonDisabled}
>
<FontAwesomeIcon icon={faDeleteLeft} className="me-2" />
{t("tts.TtsModelListPage.form.buttons.clear")}
</button>
</div>
</div>
</div>
<div className="col-12 col-lg-6">
<div className="d-flex flex-column gap-3">
<h4 className="text-center text-lg-start">
<FontAwesomeIcon
icon={faBarsStaggered}
className="me-3"
/>
{t("tts.TtsModelListPage.sessionTtsResultsLabel")}
</h4>
<div className="d-flex flex-column gap-3 session-tts-section">
<SessionTtsInferenceResultList
ttsInferenceJobs={props.ttsInferenceJobs}
sessionSubscriptionsWrapper={
props.sessionSubscriptionsWrapper
}
/>
</div>
</div>
</div>
</div>
{maybeError}
</form>
</div>
</div>
</motion.div>
</motion.div>
);
}
const LoadingIcon: React.FC = () => {
return (
<>
<span
className="spinner-border spinner-border-sm ms-3"
role="status"
aria-hidden="true"
></span>
<span className="visually-hidden">Loading...</span>
</>
);
};
export { TrumpTtsPage };
@@ -0,0 +1,81 @@
import React, { useMemo } from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
import { Link } from "react-router-dom";
import { t } from "i18next";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faStar,
faArrowRight,
faWaveformLines,
} from "@fortawesome/pro-solid-svg-icons";
import { Analytics } from "../../../../common/Analytics";
import { WebUrl } from "../../../../common/WebUrl";
import { PageHeaderWithImage } from "../../_common/PageHeaderWithImage";
interface Props {
sessionWrapper: SessionWrapper;
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
}
export function TtsPageHero(props: Props) {
const randomImage = useMemo(() => {
const images = ["mascot/trump.png"];
return images[Math.floor(Math.random() * images.length)];
}, []);
let signUpButton = <></>;
let viewPricingButton = <></>;
signUpButton = (
<>
<Link to="/">
<button type="button" className="btn btn-primary w-100">
See 2000+ more voices
<FontAwesomeIcon icon={faArrowRight} className="ms-2" />
</button>
</Link>
</>
);
viewPricingButton = (
<>
<Link
to={WebUrl.pricingPageWithReferer("tts_hero_new")}
onClick={() => {
Analytics.ttsClickHeroViewPricing();
}}
>
<button type="button" className="btn btn-secondary w-100">
<FontAwesomeIcon icon={faStar} className="me-2" />
{t("tts.TtsModelListPage.heroSection.buttons.viewPricing")}
</button>
</Link>
</>
);
const titleIcon = <FontAwesomeIcon icon={faWaveformLines} className="me-3" />;
const title = <>Donald Trump TTS</>;
const subText = (
<>Type your text below and hear it in the voice of Donald Trump.</>
);
const actionButtons = (
<>
{signUpButton}
{viewPricingButton}
</>
);
return (
<>
<PageHeaderWithImage
headerImage={randomImage}
titleIcon={titleIcon}
title={title}
subText={subText}
showButtons={true}
actionButtons={actionButtons}
/>
</>
);
}