diff --git a/packages/fakeyou.com/public/mascot/trump.png b/packages/fakeyou.com/public/mascot/trump.png new file mode 100644 index 0000000000..dcb115240e Binary files /dev/null and b/packages/fakeyou.com/public/mascot/trump.png differ diff --git a/packages/fakeyou.com/src/v2/view/PageContainer.tsx b/packages/fakeyou.com/src/v2/view/PageContainer.tsx index 5b3e46bf49..e248d681ad 100644 --- a/packages/fakeyou.com/src/v2/view/PageContainer.tsx +++ b/packages/fakeyou.com/src/v2/view/PageContainer.tsx @@ -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 { + + + + void; + ttsInferenceJobs: Array; + ttsModelUploadJobs: Array; + w2lInferenceJobs: Array; + w2lTemplateUploadJobs: Array; + textBuffer: string; + setTextBuffer: (textBuffer: string) => void; + clearTextBuffer: () => void; + + ttsModels: Array; + setTtsModels: (ttsVoices: Array) => void; + + allTtsCategories: TtsCategoryType[]; + setAllTtsCategories: (allTtsCategories: TtsCategoryType[]) => void; + + computedTtsCategoryAssignments?: GetComputedTtsCategoryAssignmentsSuccessResponse; + setComputedTtsCategoryAssignments: ( + categoryAssignments: GetComputedTtsCategoryAssignmentsSuccessResponse + ) => void; + + allTtsCategoriesByTokenMap: Map; + allTtsModelsByTokenMap: Map; + ttsModelsByCategoryToken: Map>; + + 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) => { + const textValue = (ev.target as HTMLTextAreaElement).value; + props.setTextBuffer(textValue); + setAudioLimitAlertVisible(textValue.length > 100); + }; + + const handleFormSubmit = async (ev: React.FormEvent) => { + 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) => { + 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 = ( + + You're sending too many requests! + Slow down a little. We have to slow things down a little when the + server gets busy. + + ); + break; + case GenerateTtsAudioErrorType.ServerError | + GenerateTtsAudioErrorType.BadRequest | + GenerateTtsAudioErrorType.NotFound: + break; + } + + if (hasMessage) { + maybeError = ( +
+ + {message} +
+ ); + } + } + + // 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 = ( + <> +
+ + Note: Non-premium is limited to 12 seconds of audio.{" "} + + Upgrade now + + . + +
+ + ); + } + + return ( + + + + +
+ + +
+
+ {/* Explore Rollout */} + + {/* + + + EXPLORE OMNIBAR GOES HERE TODO + + */} + +
+
+
+
+ +
+ + {audioLimitAlert} +
+ + +
+
+
+
+
+

+ + {t("tts.TtsModelListPage.sessionTtsResultsLabel")} +

+
+ +
+
+
+
+ {maybeError} +
+
+
+
+
+ ); +} + +const LoadingIcon: React.FC = () => { + return ( + <> + + Loading... + + ); +}; + +export { TrumpTtsPage }; diff --git a/packages/fakeyou.com/src/v2/view/pages/trump/TrumpTtsPageHero.tsx b/packages/fakeyou.com/src/v2/view/pages/trump/TrumpTtsPageHero.tsx new file mode 100644 index 0000000000..e599af8eb8 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/trump/TrumpTtsPageHero.tsx @@ -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 = ( + <> + + + + + ); + viewPricingButton = ( + <> + { + Analytics.ttsClickHeroViewPricing(); + }} + > + + + + ); + + const titleIcon = ; + 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 ( + <> + + + ); +}