Merge branch 'master' into bflat-storyteller-team

This commit is contained in:
BFlat authored and GitHub committed 2023-04-06 05:00:38 +07:00
commit d66aab2066
112 files changed
+3927 -1186

No files matched your search

+4 -4
View File
@@ -7,6 +7,7 @@
/checkout_success* /index.html 200
/clone* /index.html 200
/coming-soon* /index.html 200
/character* /index.html 200
/contribute* /index.html 200
/firehose* /index.html 200
/guide* /index.html 200
@@ -34,16 +35,15 @@
/upload* /index.html 200
/video* /index.html 200
/w2l* /index.html 200
# This is for YCombinator.
# This is for YCombinator.
# This is the *OLD* video for Summer 2022. We'll re-edit.
# PLEASE DO NOT ACECSS THIS URL OR WATCH. IT WILL CHANGE THE
# PLEASE DO NOT ACECSS THIS URL OR WATCH. IT WILL CHANGE THE
# VIEW STATISTICS AND WE WON'T BE ABLE TO TELL IF THEY SAW IT
#/demo https://www.youtube.com/watch?v=wfFpi-Mv3fY 302
# This is for AI Grant.
# This is the *OLD* video for Summer 2022. We'll re-edit.
# PLEASE DO NOT ACECSS THIS URL OR WATCH. IT WILL CHANGE THE
# PLEASE DO NOT ACECSS THIS URL OR WATCH. IT WILL CHANGE THE
# VIEW STATISTICS AND WE WON'T BE ABLE TO TELL IF THEY SAW IT
#/ai-grant-founder-video https://www.youtube.com/watch?v=Cj7rchSSuLg 302
#/ai-grant-demo https://www.youtube.com/watch?v=0EOZsrH7jxI 302
+3
View File
@@ -95,6 +95,9 @@ class ApiConfig {
}
}
//v2ApiHost = "api.dev.fakeyou.com:12345";
//useSsl = false;
this.domain = domain;
this.useSsl = useSsl;
this.v2ApiHost = v2ApiHost;
@@ -2,14 +2,16 @@ import { ApiConfig } from "@storyteller/components";
export interface Leaderboard {
success: boolean,
tts_leaderboard: Array<LeaderboardEntryForList>
w2l_leaderboard: Array<LeaderboardEntryForList>
tts_leaderboard: Array<LeaderboardRow>
w2l_leaderboard: Array<LeaderboardRow>
}
export interface LeaderboardEntryForList {
export interface LeaderboardRow {
username: string,
display_name: string,
gravatar_hash: string,
default_avatar_index: number,
default_avatar_color_index: number,
uploaded_count: number,
}
@@ -5,6 +5,8 @@ export interface User {
username: string,
display_name: string,
email_gravatar_hash: string,
default_avatar_index: number,
default_avatar_color_index: number,
profile_markdown: string,
profile_rendered_html: string,
user_role_slug: string,
@@ -0,0 +1,24 @@
export function getBackgroundColor(backgroundIndex?: number): string {
switch (backgroundIndex) {
case 0:
return "#E66462";
case 1:
return "#FD881B";
case 2:
return "#E7C13C";
case 3:
return "#4BA905";
case 4:
return "#25B8A0";
case 5:
return "#0078D1";
case 6:
return "#7F52C1";
case 7:
return "#FF66AC";
case 8:
return "#259FEC";
default:
return `#1a1a27`;
}
}
+15 -1
View File
@@ -1,13 +1,26 @@
import React from "react";
import { getBackgroundColor } from "./GetBGColor";
interface Props {
size: number;
email_hash?: string;
username?: string;
avatarIndex?: number;
backgroundIndex?: number;
}
function Gravatar(props: Props) {
const gravatarUrl = `https://www.gravatar.com/avatar/${props.email_hash}?s=${props.size}&d=https://fakeyou.com/images/avatars/default-pfp.png`;
// TODO: staging domain + local dev support
let defaultImageUrl =
props.avatarIndex === undefined
? "https://fakeyou.com/images/avatars/default-pfp.png"
: `https://fakeyou.com/images/avatars/2000x2000/${props.avatarIndex}.webp`;
// NB: Gravatar suggests URI encoding these:
// https://en.gravatar.com/site/implement/images/
defaultImageUrl = encodeURIComponent(defaultImageUrl);
const gravatarUrl = `https://www.gravatar.com/avatar/${props.email_hash}?s=${props.size}&d=${defaultImageUrl}`;
let altText = "gravatar";
if (props.username !== undefined) {
@@ -21,6 +34,7 @@ function Gravatar(props: Props) {
src={gravatarUrl}
height={props.size}
width={props.size}
style={{ backgroundColor: getBackgroundColor(props.backgroundIndex) }}
/>
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

@@ -18,4 +18,7 @@
<url>
<loc>https://fakeyou.com/guide</loc>
</url>
<url>
<loc>https://fakeyou.com/character/donald-trump</loc>
</url>
</urlset>
@@ -7,6 +7,6 @@ export function usePrefixedDocumentTitle(title?: string) {
// NB: Choice of a vertical bar "|" separator is due to conserving pixels, which *might* matter to SEO.
// I haven't fully investigated the veracity of this, nor the position of prefixes, suffixes, etc.
// https://www.searchenginejournal.com/pipe-or-dash-in-title-tag/378099/#close
const outputTitle = fixed.length === 0 ? DEFAULT_FOR_EMPTY_TITLE : `FakeYou | ${fixed}`;
const outputTitle = fixed.length === 0 ? DEFAULT_FOR_EMPTY_TITLE : `${fixed} | FakeYou`;
useDocumentTitle(outputTitle);
}
+29 -24
View File
@@ -55,7 +55,7 @@ styles with important tags:
select {
cursor: pointer;
border-radius: 5px;
border-radius: 0.5rem;
}
.no-wrap {
@@ -94,11 +94,11 @@ styles with important tags:
}
.rounded {
border-radius: 8px !important;
border-radius: 0.5rem !important;
}
.rounded-5 {
border-radius: 5px !important;
border-radius: 0.5rem !important;
}
code {
@@ -143,6 +143,11 @@ styles with important tags:
padding-bottom: 0.4rem !important;
}
.py-4 {
padding-top: 2rem !important;
padding-bottom: 2rem !important;
}
.lead {
color: rgba($white, 0.8);
font-size: 17px;
@@ -171,11 +176,11 @@ styles with important tags:
}
.btn {
border-radius: 5px;
border-radius: 0.5rem;
outline: none;
font-size: 15px;
font-weight: 500;
padding: 10px 24px;
padding: 10px 20px;
z-index: 2;
display: flex;
align-items: center;
@@ -275,7 +280,7 @@ styles with important tags:
}
.panel-tts-results {
border-radius: 5px;
border-radius: 0.5rem;
background-color: lighten($panel, 4%);
}
@@ -308,13 +313,13 @@ styles with important tags:
}
.menu {
border-radius: 8px !important;
border-radius: 0.5rem !important;
color: $white;
font-weight: 400;
position: absolute;
border-color: rgba($white, 0.12) !important;
border: 1px solid;
border-radius: 8px;
border-radius: 0.5rem;
box-shadow: 0px 5px 20px rgba($black, 0.3);
z-index: 3;
overflow: hidden;
@@ -353,7 +358,7 @@ styles with important tags:
}
.form-control {
border-radius: 5px;
border-radius: 0.5rem;
background-color: $input-dark !important;
border-color: $input-dark !important;
color: $white !important;
@@ -388,7 +393,7 @@ styles with important tags:
}
.alert {
border-radius: 5px;
border-radius: 0.5rem;
font-weight: 400;
font-size: 12px;
margin-bottom: 1rem;
@@ -454,7 +459,7 @@ styles with important tags:
}
#resultLink {
border-radius: 5px 0 0 5px;
border-radius: 0.5rem 0 0 5px;
}
#copyBtn {
@@ -531,7 +536,7 @@ styles with important tags:
}
.video-card {
border-radius: 5px;
border-radius: 0.5rem;
border: 4px solid;
border-color: #fff;
padding: 1rem;
@@ -625,7 +630,7 @@ styles with important tags:
color: $primary;
&.btn {
border-radius: 5px;
border-radius: 0.5rem;
font-weight: 600;
&:hover {
background-color: #fdcfce;
@@ -671,7 +676,7 @@ styles with important tags:
.direct-view-link {
background-color: lighten($panel, 4%);
border: 1px solid #39394c;
border-radius: 5px;
border-radius: 0.5rem;
padding: 12px 16px;
min-height: 63px;
height: 100%;
@@ -740,7 +745,7 @@ styles with important tags:
background: $input-dark;
margin-top: 6px;
padding: 0;
border-radius: 5px;
border-radius: 0.5rem;
z-index: 3;
}
@@ -757,7 +762,7 @@ styles with important tags:
background-color: $panel;
border-color: lighten($panel, 8%) !important;
border: 1px solid;
border-radius: 8px;
border-radius: 0.5rem;
box-shadow: 0px 9px 33px 1px rgba(19, 19, 27, 0.8);
-webkit-box-shadow: 0px 9px 33px 1px rgba(19, 19, 27, 0.8);
-moz-box-shadow: 0px 9px 33px 1px rgba(19, 19, 27, 0.8);
@@ -877,7 +882,7 @@ styles with important tags:
.twitch-chat-container {
border: 1px solid #ffffff12 !important;
border-radius: 8px;
border-radius: 0.5rem;
background-color: $panel !important;
min-height: 650px;
box-shadow: 0 0 0 1px rgba(0 0 0 / 30%), 0 70px 65px rgb(0 0 0 / 10%),
@@ -923,8 +928,8 @@ styles with important tags:
position: absolute;
width: 90%;
margin: auto;
top: 18%;
right: 6%;
top: 22%;
right: 9%;
bottom: 0;
}
@@ -976,7 +981,7 @@ styles with important tags:
}
.channel-thumbnail {
border-radius: 8px;
border-radius: 0.5rem;
border: 1px solid #ffffff15;
overflow: hidden;
transition: all 0.2s;
@@ -1146,7 +1151,7 @@ textarea {
.panel-select {
transition: 0.2s all;
cursor: pointer;
border-radius: 8px !important;
border-radius: 0.5rem !important;
max-width: 500px;
border-width: 1px !important;
@@ -1234,11 +1239,11 @@ textarea {
margin-right: auto;
margin-left: auto;
max-width: 1080px;
border-radius: 8px;
border-radius: 0.5rem;
}
.panel {
border-radius: 8px;
border-radius: 0.5rem;
}
ul.patrons-list {
@@ -1252,7 +1257,7 @@ textarea {
.twitch-video-container {
min-height: 391px;
height: 100%;
border-radius: 8px;
border-radius: 0.5rem;
}
.p-md-4 {
@@ -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/character/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="/character/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}
@@ -10,15 +10,14 @@ interface Props {
function PageHeader(props: Props) {
return (
<div className="container-panel hero-section pt-4 pt-lg-5 pb-5">
<div className="container-panel hero-section py-4">
<div className="panel">
<div className="p-3 py-4 p-md-4">
<h1 className="fw-bold text-center text-md-start">
{props.titleIcon}
{props.title}
</h1>
<hr />
<p className="text-center text-md-start">{props.subText}</p>
<p className="text-center text-md-start pt-1">{props.subText}</p>
{props.showButtons && (
<div className="d-flex flex-column flex-md-row gap-3 justify-content-center justify-content-md-start mt-4">
{props.actionButtons}
@@ -13,7 +13,7 @@ interface Props {
function PageHeaderWithImage(props: Props) {
return (
<div className="container-panel hero-section py-4 py-lg-5">
<div className="container-panel hero-section py-4">
<div className="panel">
<div className="row gx-3 flex-md-row-reverse">
<div className="col-12 col-md-5 hero-img-container d-none d-md-block">
@@ -30,10 +30,12 @@ function PageHeaderWithImage(props: Props) {
{props.titleIcon}
{props.title}
</h1>
<hr />
<p className="text-center text-md-start">{props.subText}</p>
<p className="text-center text-md-start opacity-75 pt-1">
{props.subText}
</p>
{props.showButtons && (
<div className="d-flex flex-column flex-md-row gap-3 justify-content-center justify-content-md-start mt-4">
<div className="d-flex flex-column flex-sm-row gap-3 justify-content-center justify-content-md-start mt-4 pt-2">
{props.actionButtons}
</div>
)}
@@ -109,7 +109,7 @@ function FooterNav(props: Props) {
href={ThirdPartyLinks.FAKEYOU_TIKTOK}
target="_blank"
rel="noopener noreferrer"
title="Support us by becoming a patreon"
title="Follow us on Tiktok"
>
<FontAwesomeIcon icon={faTiktok} className="me-2" />
</a>
@@ -145,10 +145,6 @@ function FooterNav(props: Props) {
<Link to="/leaderboard">Leaderboard</Link>
</li>
<li>
<Link to={WebUrl.patronsPage()}>Patrons</Link>
</li>
<li>
<Link to="/firehose">Feed</Link>
</li>
@@ -184,8 +180,10 @@ function FooterNav(props: Props) {
<div className="d-flex flex-column flex-lg-row pt-2 align-items-center gap-0 gap-lg-4">
<span className="flex-grow-1">
© 2023 FakeYou, built by{" "}
<a href="https://twitter.com/intent/follow?screen_name=echelon">echelon</a> in Atlanta.
© 2023 FakeYou by{" "}
<a href="https://storyteller.ai" target="_blank" rel="noreferrer">
Storyteller.ai
</a>
</span>
<div className="d-flex flex-column flex-lg-row align-items-center mt-4 mt-lg-0">
{moderationLink}
@@ -18,7 +18,7 @@ import {
faBook,
faStar,
} from "@fortawesome/free-solid-svg-icons";
import { faDiscord, faPatreon } from "@fortawesome/free-brands-svg-icons";
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
import { Logout } from "@storyteller/components/src/api/session/Logout";
// import { Gravatar } from "@storyteller/components/src/elements/Gravatar";
import {
@@ -96,8 +96,11 @@ function TopNav(props: Props) {
};
// TODO: We're having an outage and need to lower this.
//const interval = setInterval(async () => fetch(), 15000);
const refreshInterval = Math.max(DEFAULT_QUEUE_REFRESH_INTERVAL_MILLIS, pendingTtsJobs.refresh_interval_millis);
console.log('new interval', refreshInterval);
const refreshInterval = Math.max(
DEFAULT_QUEUE_REFRESH_INTERVAL_MILLIS,
pendingTtsJobs.refresh_interval_millis
);
console.log("new interval", refreshInterval);
const interval = setInterval(async () => fetch(), refreshInterval);
fetch();
return () => clearInterval(interval);
@@ -463,16 +466,6 @@ function TopNav(props: Props) {
{t("nav.TopNav.main.discordOption")}
</a>
</li>
<li data-bs-toggle="offcanvas">
<Link
className="dropdown-item"
to="/patrons"
title="to patron list"
>
<FontAwesomeIcon icon={faPatreon} className="me-2" />
{t("nav.TopNav.main.patronsOption")}
</Link>
</li>
<li data-bs-toggle="offcanvas">
<Link
className="dropdown-item"
@@ -7,7 +7,6 @@ import { usePrefixedDocumentTitle } from "../../../../../common/UsePrefixedDocum
interface Props {}
function AboutPage(props: Props) {
usePrefixedDocumentTitle("About Us");
return (
@@ -29,10 +28,7 @@ function AboutPage(props: Props) {
<div className="col-lg-6 px-md-2 ps-lg-5 ps-xl-2">
<div className="text-center text-lg-start">
<div>
<motion.h1
className="display-5 fw-bold lh-1 mb-4"
variants={item}
>
<motion.h1 className=" fw-bold lh-1 mb-4" variants={item}>
A Glimpse of the Future
</motion.h1>
</div>
@@ -8,7 +8,6 @@ import { usePrefixedDocumentTitle } from "../../../../../common/UsePrefixedDocum
interface Props {}
function GuidePage(props: Props) {
usePrefixedDocumentTitle("Guide to all things Deep Fake");
return (
@@ -30,10 +29,7 @@ function GuidePage(props: Props) {
<div className="col-lg-6 px-md-2 ps-lg-5 ps-xl-2">
<div className="text-center text-lg-start">
<div>
<motion.h1
className="display-5 fw-bold lh-1 mb-3"
variants={item}
>
<motion.h1 className=" fw-bold lh-1 mb-3" variants={item}>
FakeYou TTS Guide
</motion.h1>
</div>
@@ -64,81 +60,90 @@ function GuidePage(props: Props) {
<h1 className="panel-title fw-bold">Quick Tips</h1>
<div className="py-6 d-flex flex-column gap-4">
<p>
Here are some quick tips to generate the best sounding text-to-speech.
Here are some quick tips to generate the best sounding
text-to-speech.
</p>
<div>
<h2 className="mb-4">Fixing nonsense &ldquo;babbling&rdquo;</h2>
<p>(This is not a typical problem.)</p>
<br />
<p>
(This is not a typical problem.)
Some of our very earliest models were built using a limited
amount of data and are sometimes prone to producing speech that
sounds like &ldquo;babbling&rdquo; or incoherent vocal
convulsions.
</p>
<br />
<p>
Some of our very earliest models were built using a limited amount of data
and are sometimes prone to producing speech that sounds like &ldquo;babbling&rdquo;
or incoherent vocal convulsions.
This problem can typically be fixed by{" "}
<strong>
<em>adding ending punctuation to your sentence</em>
</strong>
. If the audio still has problems,{" "}
<strong>
<em>add a comma at the start and end of your text</em>
</strong>{" "}
so that the program can add brief pauses. This usually corrects
the problem.
</p>
<br />
<p>
This problem can typically be fixed by
{" "}
<strong><em>adding ending punctuation to your sentence</em></strong>.
If the audio still has problems,
{" "}
<strong><em>add a comma at the start and end of your text</em></strong>
{" "}
so that the program can add brief pauses. This usually corrects the problem.
</p>
<br />
<p>
If the incoherent audio is at the end, you can always edit it with software
such as Audacity.
If the incoherent audio is at the end, you can always edit it
with software such as Audacity.
</p>
</div>
<div>
<h2 className="mb-4">
Getting the correct pronunciation
</h2>
<h2 className="mb-4">Getting the correct pronunciation</h2>
<p>
Sometimes a TTS model will not pronounce words correctly or in the
way you like.
You can <strong><em>experiment with misspellings</em></strong> of words until you get something
that sounds better. For example <em>&ldquo;peekahchuu&rdquo;</em> might sound different
than <em>&ldquo;pikachu&rdquo;</em>.
Sometimes a TTS model will not pronounce words correctly or in
the way you like. You can{" "}
<strong>
<em>experiment with misspellings</em>
</strong>{" "}
of words until you get something that sounds better. For example{" "}
<em>&ldquo;peekahchuu&rdquo;</em> might sound different than{" "}
<em>&ldquo;pikachu&rdquo;</em>.
</p>
<br />
<p>
Many of our models also support Arpabet phonetic annotation. It's an advanced feature you can
learn to use by joining our <DiscordLink2 />.
Many of our models also support Arpabet phonetic annotation.
It's an advanced feature you can learn to use by joining our{" "}
<DiscordLink2 />.
</p>
</div>
<div>
<h2 className="mb-4">Emotional conditioning</h2>
<p>
To get a certain emotion and personality in the message you
want, add some extra emotional &ldquo;conditioning&rdquo;.
want, add some extra emotional &ldquo;conditioning&rdquo;.
</p>
<br />
<p>
To do this, think of a strong emotional sentence or two you could start with.
Then add this to the very beginning of your text.
For example, to add some anger to <em>&ldquo;I can't believe you said that to me!&rdquo;</em>,
you could input the following instead:
To do this, think of a strong emotional sentence or two you
could start with. Then add this to the very beginning of your
text. For example, to add some anger to{" "}
<em>&ldquo;I can't believe you said that to me!&rdquo;</em>, you
could input the following instead:
</p>
<br />
<p>
<em>&ldquo;<strong>Darn it! I hate you!</strong> I can't believe you said that to me!&rdquo;</em>
<em>
&ldquo;<strong>Darn it! I hate you!</strong> I can't believe
you said that to me!&rdquo;
</em>
</p>
<br />
<p>
You can later remove these extra leading sentences with an audio editor such as Audacity, leaving
just the speech you want.
You can later remove these extra leading sentences with an audio
editor such as Audacity, leaving just the speech you want.
</p>
</div>
<div>
<h2 className="mb-4">Yelling emotions</h2>
<p>
While not all models support it, some of our models incorporate rich emotional samples
that were transcripted with an emphasis mark &ndash; typically an exclamation mark (!).
While not all models support it, some of our models incorporate
rich emotional samples that were transcripted with an emphasis
mark &ndash; typically an exclamation mark (!).
</p>
</div>
</div>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -208,7 +208,7 @@ function CreateCategoryPage(props: Props) {
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pb-4 pt-5 px-md-4 px-lg-5 px-xl-3">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
{categoryActionName} Category
</motion.h1>
<motion.div className="pt-3" variants={item}>
@@ -48,7 +48,7 @@ function ChannelsPage(props: Props) {
animate="visible"
variants={container}
>
<motion.h1 className="display-5 fw-bold mb-4" variants={item}>
<motion.h1 className=" fw-bold mb-4" variants={item}>
Channels
</motion.h1>
@@ -0,0 +1,506 @@
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";
import { SearchOmnibar } from "./search/SearchOmnibar";
const PAGE_MODEL_TOKENS = new Set<string>([
"TM:pmd1wm3kf6az", // Development: "Fake Donald Trump #1"
"TM:7rrwdhdq8ezq", // Development: "Fake Donald Trump #2"
//"TM:aejrk66wq3ss", // Production: "Donald Trump (bibby's model)"
"TM:pyzss4phqk6r", // Production: "Donald Trump (Sarcastic)"
"TM:4v0ft4j72y2g", // Production: "Donald Trump (Angry)"
"TM:03690khwpsbz", // Production: "Donald Trump (Casual Speech)"
]);
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);
usePrefixedDocumentTitle("Donald Trump TTS and Donald Trump AI Voice");
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)];
//}
let trumpModels = models.filter((model) => {
return PAGE_MODEL_TOKENS.has(model.model_token);
})
let model = trumpModels[Math.floor(Math.random() * trumpModels.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;
};
// 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>
</>
);
}
let trumpModels = props.ttsModels.filter((model) => {
return PAGE_MODEL_TOKENS.has(model.model_token);
})
let maybeSelectedModel = props.maybeSelectedTtsModel;
if (maybeSelectedModel !== undefined && !PAGE_MODEL_TOKENS.has(maybeSelectedModel.model_token)) {
// If we don't select a trump model by default (which is rare), the list will be empty.
// We'll set it here.
maybeSelectedModel = trumpModels[0];
}
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 */}
<SearchOmnibar
allTtsCategories={props.allTtsCategories}
allTtsModels={trumpModels}
allTtsCategoriesByTokenMap={props.allTtsCategoriesByTokenMap}
allTtsModelsByTokenMap={props.allTtsModelsByTokenMap}
ttsModelsByCategoryToken={props.ttsModelsByCategoryToken}
dropdownCategories={props.dropdownCategories}
setDropdownCategories={props.setDropdownCategories}
selectedCategories={props.selectedCategories}
setSelectedCategories={props.setSelectedCategories}
maybeSelectedTtsModel={maybeSelectedModel}
setMaybeSelectedTtsModel={props.setMaybeSelectedTtsModel}
selectedTtsLanguageScope={props.selectedTtsLanguageScope}
setSelectedTtsLanguageScope={props.setSelectedTtsLanguageScope}
/>
{/*
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,
faVolume,
} 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.webp"];
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={faVolume} className="me-3" />;
const title = <>Donald Trump TTS</>;
const subText = (
<>FakeYou has the very best Donald Trump AI voice on the internet. Use deep fake Donald Trump to say your favorite memes.</>
);
const actionButtons = (
<>
{signUpButton}
{viewPricingButton}
</>
);
return (
<>
<PageHeaderWithImage
headerImage={randomImage}
titleIcon={titleIcon}
title={title}
subText={subText}
showButtons={true}
actionButtons={actionButtons}
/>
</>
);
}
@@ -0,0 +1,59 @@
import React from "react";
import { TtsCategoryType } from "../../../../../../AppWrapper";
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
import { ScopedVoiceModelOptions } from "./components/ScopedVoiceModelOptions";
import { VoiceCountLabel } from "./components/VoiceCountLabel";
interface Props {
allTtsCategories: TtsCategoryType[];
allTtsModels: TtsModelListItem[];
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;
}
export function SearchOmnibar(props: Props) {
return (
<>
<div className="pb-4">
<VoiceCountLabel
allTtsModels={props.allTtsModels}
ttsModelsByCategoryToken={props.ttsModelsByCategoryToken}
selectedCategories={props.selectedCategories}
selectedTtsLanguageScope={props.selectedTtsLanguageScope}
setMaybeSelectedTtsModel={props.setMaybeSelectedTtsModel}
/>
<div className="d-flex flex-column flex-lg-row gap-3">
<div className="flex-grow-1">
<ScopedVoiceModelOptions
allTtsCategories={props.allTtsCategories}
allTtsModels={props.allTtsModels}
allTtsCategoriesByTokenMap={props.allTtsCategoriesByTokenMap}
allTtsModelsByTokenMap={props.allTtsModelsByTokenMap}
ttsModelsByCategoryToken={props.ttsModelsByCategoryToken}
dropdownCategories={props.dropdownCategories}
selectedCategories={props.selectedCategories}
maybeSelectedTtsModel={props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={props.setMaybeSelectedTtsModel}
selectedTtsLanguageScope={props.selectedTtsLanguageScope}
/>
</div>
</div>
</div>
</>
);
}
@@ -0,0 +1,209 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
import { TtsCategoryType } from "../../../../../../../AppWrapper";
import Select, { createFilter } from "react-select";
import { SearchFieldClass } from "./SearchFieldClass";
import { FastReactSelectOption } from "../../../../../_common/react_select/FastReactSelectOption";
import { Analytics } from "../../../../../../../common/Analytics";
import { FixedSingleValueSelectOption } from "../../../../../_common/react_select/FixedSingleValueSelectOption";
import { faMicrophone } from "@fortawesome/pro-duotone-svg-icons";
interface Props {
allTtsCategories: TtsCategoryType[];
allTtsModels: TtsModelListItem[];
allTtsCategoriesByTokenMap: Map<string, TtsCategoryType>;
allTtsModelsByTokenMap: Map<string, TtsModelListItem>;
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>;
dropdownCategories: TtsCategoryType[][];
selectedCategories: TtsCategoryType[];
maybeSelectedTtsModel?: TtsModelListItem;
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void;
selectedTtsLanguageScope: string;
}
export function ScopedVoiceModelOptions(props: Props) {
const {
allTtsModels,
ttsModelsByCategoryToken,
selectedCategories,
maybeSelectedTtsModel,
} = props;
//const { t } = useTranslation();
const handleChange = (option: any, actionMeta: any) => {
const ttsModelToken = option?.value;
const maybeNewTtsModel = props.allTtsModelsByTokenMap.get(ttsModelToken);
if (maybeNewTtsModel !== undefined) {
props.setMaybeSelectedTtsModel(maybeNewTtsModel);
}
};
const leafiestCategory = selectedCategories[selectedCategories.length - 1];
let leafiestCategoryModels: Array<TtsModelListItem> = [];
if (leafiestCategory !== undefined) {
leafiestCategoryModels = Array.from(
ttsModelsByCategoryToken.get(leafiestCategory.category_token) || new Set()
);
} else {
leafiestCategoryModels = Array.from(new Set(allTtsModels));
}
interface DropdownOption {
label: string;
value: string;
creatorName?: string;
}
let options: DropdownOption[] = leafiestCategoryModels
.filter((ttsModel) => {
// Scope to currently selected language
if (props.selectedTtsLanguageScope === "*") {
return true; // NB: Sentinel value of "*" means all languages.
}
return (
ttsModel.ietf_primary_language_subtag === props.selectedTtsLanguageScope
);
})
.map((ttsModel) => {
return {
label: ttsModel.title,
value: ttsModel.model_token,
creatorName: ttsModel.creator_display_name,
};
});
let selectedOption = options.find(
(option) => option.value === maybeSelectedTtsModel?.model_token
);
if (selectedOption === undefined && options.length > 0) {
// NB: We shouldn't select the first item in the list since that won't update the currently
// selected model. If the user were to close the dialogue, they'd think they had picked a voice,
// when in reality no state would have changed. By forcing the user to choose, the user will set
// the state appropriately.
selectedOption = {
label: "Select voice...",
value: "*",
creatorName: undefined,
};
}
let isLoading = false;
if (props.allTtsModels.length === 0) {
// NB: react-select will cache values, even across different instances (!!!)
// This can cause confusion when initializing a select instance before the data
// is loaded, and the select will never update to show the new data.
// The proper way to change voices after load from a placeholder "Loading..."
// label is to use controlled props / value as is done here:
isLoading = true;
selectedOption = {
label: "Loading...",
value: "*",
creatorName: undefined,
};
} else if (options.length === 0) {
// NB: Perhaps the user has refined their search to be too narrow (langauge + category)
selectedOption = {
label: "No results (remove some filters)",
value: "*",
creatorName: undefined,
};
}
const numberVoices = options.length;
const canSearchVoices =
numberVoices > 100 || // Always allow search when there are over 100 voices
window.innerWidth >= 1000; // Always allow search when on desktop
let select;
// Function to build the options themselves, so we can introduce extra elements.
const formatOptionLabel = (
data: DropdownOption,
formatOptionLabelMeta: any
) => {
let creatorName = <></>;
if (data.creatorName !== undefined) {
creatorName = <span className="opacity-50"> — {data.creatorName}</span>;
}
return (
<div>
{data.label}
{creatorName}
</div>
);
};
// TODO: Cleanup
if (canSearchVoices) {
select = (
<Select
value={selectedOption} // Controlled components use "value" instead of "defaultValue".
options={options}
classNames={SearchFieldClass}
onChange={handleChange}
onMenuOpen={() => {
Analytics.ttsOpenPrimaryVoiceSelectMenu();
}}
isLoading={isLoading}
isSearchable={true}
// NB: The following settings improve upon performance.
// See: https://github.com/JedWatson/react-select/issues/3128
filterOption={createFilter({ ignoreAccents: false })}
components={
{
SingleValue: FixedSingleValueSelectOption,
Option: FastReactSelectOption,
} as any
}
formatOptionLabel={formatOptionLabel}
/>
);
} else {
select = (
<Select
value={selectedOption}
options={options}
classNames={SearchFieldClass}
onChange={handleChange}
onMenuOpen={() => {
Analytics.ttsOpenScopedVoiceSelectMenu();
}}
isLoading={isLoading}
// On mobile, we don't want the onscreen keyboard to take up half the UI.
autoFocus={false}
isSearchable={false}
// NB: The following settings improve upon performance.
// See: https://github.com/JedWatson/react-select/issues/3128
filterOption={createFilter({ ignoreAccents: false })}
components={{ Option: FastReactSelectOption } as any}
formatOptionLabel={formatOptionLabel}
/>
);
}
return (
<>
<div className="col">
<div className="input-icon-search">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faMicrophone} />
</span>
{select}
</div>
</div>
</>
);
}
@@ -0,0 +1,14 @@
export const SearchFieldClass = {
control: (state: any) =>
state.isFocused ? "select-search focused" : "select-search",
option: (state: any) => (state.isFocused ? "select-option" : "select-option"),
input: (state: any) => (state.isFocused ? "select-input" : "select-input"),
placeholder: (state: any) =>
state.isFocused ? "select-placeholder" : "select-placeholder",
singleValue: (state: any) =>
state.isFocused ? "select-value" : "select-value",
menu: (state: any) =>
state.isFocused ? "select-container" : "select-container",
indicatorSeparator: (state: any) =>
state.isFocused ? "select-separator" : "select-separator",
};
@@ -0,0 +1,97 @@
import React from "react";
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
import { TtsCategoryType } from "../../../../../../../AppWrapper";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faShuffle } from "@fortawesome/pro-solid-svg-icons";
import { Analytics } from "../../../../../../../common/Analytics";
import { GetRandomArrayValue } from "@storyteller/components/src/utils/GetRandomArrayValue";
import Tippy from "@tippyjs/react";
import "tippy.js/dist/tippy.css";
interface Props {
allTtsModels: TtsModelListItem[];
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>;
selectedCategories: TtsCategoryType[];
selectedTtsLanguageScope: string;
setMaybeSelectedTtsModel: (maybeSelectedTtsModel: TtsModelListItem) => void;
}
// NB/TODO: This duplicates the work of a sister component, but it was the fastest way
// to hack this in without passing callbacks around or moving calculation higher up the
// component tree.
export function VoiceCountLabel(props: Props) {
const { allTtsModels, ttsModelsByCategoryToken, selectedCategories } = props;
const leafiestCategory = selectedCategories[selectedCategories.length - 1];
let leafiestCategoryModels: Array<TtsModelListItem> = [];
if (leafiestCategory !== undefined) {
leafiestCategoryModels = Array.from(
ttsModelsByCategoryToken.get(leafiestCategory.category_token) || new Set()
);
} else {
leafiestCategoryModels = Array.from(new Set(allTtsModels));
}
let possibleVoices = leafiestCategoryModels.filter((ttsModel) => {
// Scope to currently selected language
if (props.selectedTtsLanguageScope === "*") {
return true; // NB: Sentinel value of "*" means all languages.
}
return (
ttsModel.ietf_primary_language_subtag === props.selectedTtsLanguageScope
);
});
const selectRandomVoice = () => {
let randomVoice = undefined;
for (let i = 0; i < 10; i++) {
// We're going to try to find a *good* voice.
randomVoice = GetRandomArrayValue(possibleVoices);
if (
randomVoice.user_ratings.positive_count >
randomVoice.user_ratings.negative_count
) {
break; // First "mostly good" voice is good.
}
if (i % 3 === 2 && randomVoice.user_ratings.total_count === 0) {
break; // Sometimes we'll return a voice that hasn't been rated.
}
}
if (randomVoice !== undefined) {
props.setMaybeSelectedTtsModel(randomVoice);
}
};
const voiceCount = possibleVoices.length;
return (
<>
<div className="d-flex gap-2">
<label className="sub-title">
Choose a Donald Trump Voice ({voiceCount} to choose from)
<Tippy
content="Random Voice"
hideOnClick
placement="top"
theme="fakeyou"
arrow={false}
>
<button
onClick={() => {
Analytics.ttsClickRandomVoice();
selectRandomVoice();
}}
className="btn-link pt-0 ps-2 pe-0"
type="button"
>
<FontAwesomeIcon icon={faShuffle} />
</button>
</Tippy>
</label>
</div>
</>
);
}
@@ -0,0 +1,197 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowRightLong, faTags } from "@fortawesome/free-solid-svg-icons";
import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
import { TtsCategoryType } from "../../../../../../../AppWrapper";
//import { useTranslation } from "react-i18next";
import Select from "react-select";
import { SearchFieldClass } from "../components/SearchFieldClass";
import { Analytics } from "../../../../../../../common/Analytics";
import { t } from "i18next";
interface Props {
allTtsCategories: TtsCategoryType[];
allTtsModels: TtsModelListItem[];
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;
handleChangeCategory: (level: number, maybeToken?: string) => void;
selectedTtsLanguageScope: string;
}
export function CategoryOptions(props: Props) {
const {
ttsModelsByCategoryToken,
dropdownCategories,
selectedCategories,
handleChangeCategory,
} = props;
//const { t } = useTranslation();
let categoryDropdowns = buildDropdowns(
dropdownCategories,
selectedCategories,
ttsModelsByCategoryToken,
handleChangeCategory
);
const CATEGORY_SEPARATOR = (
<div className="d-none d-md-flex align-items-center">
<FontAwesomeIcon icon={faArrowRightLong} className="fs-6 opacity-75" />
</div>
);
let categoryDropdownsWithSeparators = [];
for (let i = 0; i < categoryDropdowns.length; i++) {
categoryDropdownsWithSeparators.push(categoryDropdowns[i]);
if (i < categoryDropdowns.length - 1) {
categoryDropdownsWithSeparators.push(CATEGORY_SEPARATOR);
}
}
return (
<>
<div className="d-flex flex-column flex-md-row gap-2">
{categoryDropdownsWithSeparators}
</div>
</>
);
}
// ========= Build dropdowns ========
function buildDropdowns(
dropdownCategories: TtsCategoryType[][],
selectedCategories: TtsCategoryType[],
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>,
handleChangeCategory: (i: number, categoryToken?: string) => void
) {
if (dropdownCategories.length === 0 || ttsModelsByCategoryToken.size === 0) {
// While the XHR requests are still completing, we may have nothing to build.
// It's easier to return a fully disabled "loading" <Select /> component.
return [
<div className="w-100">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faTags} />
</span>
<Select
isLoading={true}
options={[]}
inputValue={"Loading..."}
classNames={SearchFieldClass}
className={"w-100"}
/>
</div>,
];
}
let categoryDropdowns = [];
for (let i = 0; i < dropdownCategories.length; i++) {
const currentDropdownCategories = dropdownCategories[i];
const selectedCategory = selectedCategories[i];
let defaultName = i === 0 ? "All Voices" : "Select...";
let dropdownOptions = [];
dropdownOptions.push(
<option key={`option-${i}-*`} value="*">
{defaultName}
</option>
);
// TODO(bt, 2023-01-18): Clean this up
// Transform into "react-select" library compatible options
const options = currentDropdownCategories
.filter((category) => {
// If there are no models at the leaves, skip the category
const models = ttsModelsByCategoryToken.get(category.category_token);
return !(models === undefined || models.size === 0);
})
.map((category) => {
return {
value: category.category_token,
label: category.name_for_dropdown,
};
});
currentDropdownCategories.forEach((category) => {
const models = ttsModelsByCategoryToken.get(category.category_token);
if (models === undefined || models.size === 0) {
return; // If there are no models at the leaves, skip
}
dropdownOptions.push(
<option
key={`option-${i}-${category.category_token}`}
value={category.category_token}
>
{category.name_for_dropdown}
</option>
);
});
let selectedCategoryOption = undefined;
if (selectedCategory !== undefined) {
selectedCategoryOption = {
value: selectedCategory.category_token,
label: selectedCategory.name_for_dropdown,
};
}
if (dropdownOptions.length <= 1) {
// We've run out of subcategories. (1 == "Select...")
// No sense trying to build more.
break;
}
let selectProps: any = {
options: options,
classNames: SearchFieldClass,
className: "w-100",
autoFocus: false, // On mobile, we don't want the onscreen keyboard to take up half the UI.
isSearchable: false, // On mobile, we don't want the onscreen keyboard to take up half the UI.
onMenuOpen: () => {
Analytics.ttsOpenCategorySelectMenu();
},
onChange: (option: any) => handleChangeCategory(i, option?.value),
};
if (selectedCategoryOption === undefined) {
// NB(bt, 2023-01-19): I'm not sure why we're having to do this to clear categories.
// If I had more time to spend with this library, I might have a better solution than this hack.
selectProps["value"] = {
value: "*",
label: t("tts.TtsModelListPage.exploreModal.selectCategoryText"),
};
} else {
selectProps["value"] = selectedCategoryOption;
}
categoryDropdowns.push(
<React.Fragment key={`categoryDropdown-${i}`}>
<div className="w-100">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faTags} />
</span>
<Select {...selectProps} />
</div>
</React.Fragment>
);
}
return categoryDropdowns;
}
@@ -0,0 +1,69 @@
import React from "react";
import { faGlobe } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Select from "react-select";
import { SearchFieldClass } from "../components/SearchFieldClass";
import { AVAILABLE_TTS_LANGUAGE_CATEGORY_MAP } from "../../../../../../../_i18n/AvailableLanguageMap";
import { Analytics } from "../../../../../../../common/Analytics";
import { t } from "i18next";
interface Props {
selectedTtsLanguageScope: string;
setSelectedTtsLanguageScope: (selectedTtsLanguageScope: string) => void;
}
export function LanguageOptions(props: Props) {
const handleChange = (option: any, actionMeta: any) => {
props.setSelectedTtsLanguageScope(option.value);
};
let languageOptions = Object.entries(AVAILABLE_TTS_LANGUAGE_CATEGORY_MAP).map(
([languageCode, language]) => {
let label = `${language.languageName}`;
if (language.languageNameLocalized !== undefined) {
label = `${language.languageNameLocalized} / ${label}`;
}
if (language.flags.length > 0) {
label += ` ${language.flags.join(" ")}`;
}
return {
value: languageCode,
label: label,
};
}
);
languageOptions = [
{
label: `${t("tts.TtsModelListPage.exploreModal.allLanguagesOptionText")}`,
value: "*",
},
...languageOptions,
];
const currentValue =
languageOptions.find((option) => {
return option.value === props.selectedTtsLanguageScope;
}) || languageOptions[0];
return (
<div>
<span className="form-control-feedback">
<FontAwesomeIcon icon={faGlobe} />
</span>
<Select
value={currentValue}
options={languageOptions}
classNames={SearchFieldClass}
isSearchable={false}
onChange={handleChange}
onMenuOpen={() => {
Analytics.ttsOpenCategorySelectMenu();
}}
/>
</div>
);
}
@@ -30,7 +30,6 @@ import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocument
interface Props {}
function VoiceCloneRequestPage(props: Props) {
usePrefixedDocumentTitle("Create a Custom Voice Clone with Deep Fake TTS");
// Contact
@@ -262,7 +261,7 @@ function VoiceCloneRequestPage(props: Props) {
</div>
<div className="col-lg-6 px-3 px-md-2 px-lg-5 px-xl-2">
<div className="text-center text-lg-start">
<motion.h1 className="display-5 fw-bold lh-1" variants={item}>
<motion.h1 className=" fw-bold lh-1" variants={item}>
Professionally Clone Your Voice
</motion.h1>
<motion.h3 className="mb-4" variants={item}>
@@ -44,7 +44,7 @@ function ContributeIndexPage(props: Props) {
You make FakeYou better by contributing
</Trans>
</h5>
{t("pages.contributeIndex.introText")}
<div className="opacity-75">{t("pages.contributeIndex.introText")}</div>
</>
);
const titleIcon = (
@@ -55,7 +55,7 @@ function ContributeIndexPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
{/* <div className="container py-5 px-md-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
{t("pages.contributeIndex.heroTitle")}
</motion.h1>
<motion.h3 className="mb-4" variants={item}>
@@ -29,6 +29,9 @@ interface FirehoseEventListResponsePayload {
interface FirehoseEvent {
event_token: string;
event_type: string;
maybe_target_user_info?: TargetUserInfo;
maybe_target_user_token?: string;
maybe_target_username?: string;
maybe_target_display_name?: string;
@@ -38,6 +41,15 @@ interface FirehoseEvent {
updated_at: string;
}
interface TargetUserInfo {
user_token: string;
username: string;
display_name: string;
gravatar_hash: string;
default_avatar_index: number;
default_avatar_color_index: number;
}
interface Props {
sessionWrapper: SessionWrapper;
}
@@ -95,7 +107,19 @@ function FirehoseEventListPage(props: Props) {
firehoseEvents.slice(0, 16).forEach((event) => {
let inner = <span />;
let userLink = <span>Anonymous user</span>;
let gravatar = <span />;
let gravatar = (
<div className="me-2">
<Gravatar
size={32}
username={event.maybe_target_username}
email_hash={event.maybe_target_user_gravatar_hash}
avatarIndex={event.maybe_target_user_info?.default_avatar_index || 0}
backgroundIndex={
event.maybe_target_user_info?.default_avatar_color_index
}
/>
</div>
);
if (
event.maybe_target_username !== undefined &&
@@ -106,11 +130,17 @@ function FirehoseEventListPage(props: Props) {
let link = `/profile/${event.maybe_target_username}`;
userLink = <Link to={link}>{event.maybe_target_display_name}</Link>;
gravatar = (
<div className="me-1">
<div className="me-2">
<Gravatar
size={22}
size={32}
username={event.maybe_target_username}
email_hash={event.maybe_target_user_gravatar_hash}
avatarIndex={
event.maybe_target_user_info?.default_avatar_index || 0
}
backgroundIndex={
event.maybe_target_user_info?.default_avatar_color_index
}
/>
</div>
);
@@ -122,7 +152,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faPenToSquare} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;signed up for FakeYou!
</span>
@@ -133,7 +162,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faAward} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;got a badge!
</span>
@@ -144,7 +172,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faPlay} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;started TTS model upload
</span>
@@ -155,7 +182,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faFlagCheckered} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;completed TTS model upload
</span>
@@ -166,7 +192,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faPlay} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;started TTS
</span>
@@ -177,7 +202,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faFlagCheckered} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;completed TTS
</span>
@@ -188,7 +212,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faPlay} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;started uploading a lipsync template.
</span>
@@ -199,7 +222,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faFlagCheckered} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;finished uploading a lipsync template.
</span>
@@ -210,7 +232,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faPlay} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;started a W2L lipsync video
</span>
@@ -221,7 +242,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faFlagCheckered} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;completed a W2L lipsync video
</span>
@@ -232,7 +252,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faTwitter} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;mentioned us on twitter!
</span>
@@ -243,7 +262,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faTwitter} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;retweeted us!
</span>
@@ -254,7 +272,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faDiscord} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;joined discord!
</span>
@@ -265,7 +282,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faDiscord} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;sent a discord message
</span>
@@ -276,7 +292,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faTwitch} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;subscribed to us on twitch!
</span>
@@ -287,7 +302,6 @@ function FirehoseEventListPage(props: Props) {
<span className="d-flex align-items-center">
<FontAwesomeIcon icon={faTwitch} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp;followed us on twitch!
</span>
@@ -310,7 +324,7 @@ function FirehoseEventListPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container py-5 px-md-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
Firehose Event Feed
</motion.h1>
<motion.h4 className="mb-4" variants={item}>
@@ -8,7 +8,7 @@ import {
GetLeaderboardIsErr,
GetLeaderboardIsOk,
Leaderboard,
LeaderboardEntryForList,
LeaderboardRow,
LeaderboardLookupError,
} from "@storyteller/components/src/api/leaderboard/GetLeaderboard";
import { DiscordLink2 } from "@storyteller/components/src/elements/DiscordLink2";
@@ -25,10 +25,10 @@ function LeaderboardPage(props: Props) {
undefined
);
const [ttsLeaderboard, setTtsLeaderboard] = useState<
Array<LeaderboardEntryForList> | undefined
Array<LeaderboardRow> | undefined
>(undefined);
const [w2lLeaderboard, setW2lLeaderboard] = useState<
Array<LeaderboardEntryForList> | undefined
Array<LeaderboardRow> | undefined
>(undefined);
const [retryCount, setRetryCount] = useState(0);
@@ -70,13 +70,17 @@ function LeaderboardPage(props: Props) {
ttsRows.push(
<tr>
<td className="lb-name">
<Link to={WebUrl.userProfilePage(ttsEntry.display_name)}>
<Link
to={WebUrl.userProfilePage(ttsEntry.display_name)}
className="d-flex align-items-center gap-2"
>
<Gravatar
size={12}
size={32}
username={ttsEntry.display_name}
email_hash={ttsEntry.gravatar_hash}
avatarIndex={ttsEntry.default_avatar_index}
backgroundIndex={ttsEntry.default_avatar_color_index}
/>
&nbsp;
{ttsEntry.display_name}
</Link>
</td>
@@ -93,13 +97,17 @@ function LeaderboardPage(props: Props) {
w2lRows.push(
<tr>
<td className="lb-name">
<Link to={WebUrl.userProfilePage(w2lEntry.display_name)}>
<Link
to={WebUrl.userProfilePage(w2lEntry.display_name)}
className="d-flex align-items-center gap-2"
>
<Gravatar
size={12}
size={32}
username={w2lEntry.display_name}
email_hash={w2lEntry.gravatar_hash}
avatarIndex={w2lEntry.default_avatar_index}
backgroundIndex={w2lEntry.default_avatar_color_index}
/>
&nbsp;
{w2lEntry.display_name}
</Link>
</td>
@@ -113,7 +121,7 @@ function LeaderboardPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container py-5 px-md-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
Leaderboard
</motion.h1>
<motion.h3 className="mb-4" variants={item}>
@@ -138,16 +138,13 @@ function ModerationIpBanListFc(props: Props) {
return (
<div>
<div className="container py-5">
<h1 className="display-5 fw-bold">Moderation IP Ban List</h1>
<h1 className=" fw-bold">Moderation IP Ban List</h1>
<p>
IP Address bans will prevent bad actors from using and abusing the
website.
</p>
<div className="pt-4">
<BackLink
link={WebUrl.moderationMain()}
text="Back to moderation"
/>
<BackLink link={WebUrl.moderationMain()} text="Back to moderation" />
</div>
</div>
@@ -104,12 +104,9 @@ function ModerationJobStatsFc(props: Props) {
return (
<div>
<div className="container py-5">
<h1 className="display-5 fw-bold">Job Stats</h1>
<h1 className=" fw-bold">Job Stats</h1>
<div className="pt-3">
<BackLink
link={WebUrl.moderationMain()}
text="Back to moderation"
/>
<BackLink link={WebUrl.moderationMain()} text="Back to moderation" />
</div>
</div>
@@ -246,10 +243,7 @@ function ModerationJobStatsFc(props: Props) {
</div>
<div className="container pb-5">
<BackLink
link={WebUrl.moderationMain()}
text="Back to moderation"
/>
<BackLink link={WebUrl.moderationMain()} text="Back to moderation" />
</div>
</div>
);
@@ -24,7 +24,7 @@ function ModerationFc(props: Props) {
return (
<div>
<div className="container py-5">
<h1 className="display-5 fw-bold text-center text-lg-start">
<h1 className=" fw-bold text-center text-lg-start">
Moderation Controls
</h1>
</div>
@@ -13,7 +13,6 @@ interface Props {
}
function PatronPage(props: Props) {
usePrefixedDocumentTitle("Thank you to our Patrons!");
return (
@@ -21,7 +20,7 @@ function PatronPage(props: Props) {
<div className="container py-5 pt-lg-0">
<div className="row">
<div className="col-12 col-lg-7 d-flex flex-column justify-content-center text-center text-lg-start px-3 px-md-2 px-lg-5 px-xl-3">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
Thanks to our Patrons!
</motion.h1>
<motion.h3 className="mb-4 px-4 px-md-0" variants={item}>
@@ -29,28 +29,25 @@ function CheckoutCancelPage(props: Props) {
<div className="col-lg-6 px-md-2 ps-lg-5 ps-xl-2">
<div className="text-center text-lg-start">
<div>
<motion.h1
className="display-5 fw-bold lh-1 mb-4"
variants={item}
>
<motion.h1 className=" fw-bold lh-1 mb-4" variants={item}>
Oh no!
</motion.h1>
</div>
<div>
<motion.p className="lead" variants={item}>
It's okay if you don't want to support FakeYou right now, but we'd
appreciate it if you reconsider us in the future.
It's okay if you don't want to support FakeYou right now, but
we'd appreciate it if you reconsider us in the future.
</motion.p>
<br />
<motion.p className="lead" variants={item}>
Paid plans go directly to helping us afford more GPUs, engineering,
and research talent.
Paid plans go directly to helping us afford more GPUs,
engineering, and research talent.
</motion.p>
<br />
<motion.p className="lead" variants={item}>
We're trying to build a film and music production system that you
can use to make any content you dream up. Please consider supporting
us monetarily.
We're trying to build a film and music production system that
you can use to make any content you dream up. Please consider
supporting us monetarily.
</motion.p>
</div>
</div>
@@ -65,14 +62,12 @@ function CheckoutCancelPage(props: Props) {
<div className="col-lg-6">
<div className="d-flex justify-content-center">
<Link to="/pricing" className="btn btn-primary fs-6">
Select a Premium Plan
</Link>
</div>
</div>
</div>
</div>
</motion.div>
);
}
@@ -1,4 +1,3 @@
import React from "react";
import { Link } from "react-router-dom";
import { motion } from "framer-motion";
@@ -30,20 +29,17 @@ function CheckoutSuccessPage(props: Props) {
<div className="col-lg-6 px-md-2 ps-lg-5 ps-xl-2">
<div className="text-center text-lg-start">
<div>
<motion.h1
className="display-5 fw-bold lh-1 mb-4"
variants={item}
>
<motion.h1 className=" fw-bold lh-1 mb-4" variants={item}>
Thank you!
</motion.h1>
</div>
<br />
<div>
<motion.p className="lead" variants={item}>
We're grateful you're helping FakeYou build the future creative
suite. We're building tools for small creators and have an eye
on building a full AI-powered Hollywood and music production
studio.
We're grateful you're helping FakeYou build the future
creative suite. We're building tools for small creators and
have an eye on building a full AI-powered Hollywood and music
production studio.
</motion.p>
<br />
<motion.p className="lead" variants={item}>
@@ -65,7 +61,6 @@ function CheckoutSuccessPage(props: Props) {
</div>
</div>
</div>
</motion.div>
);
}
@@ -29,10 +29,7 @@ function PortalSuccessPage(props: Props) {
<div className="col-lg-6 px-md-2 ps-lg-5 ps-xl-2">
<div className="text-center text-lg-start">
<div>
<motion.h1
className="display-5 fw-bold lh-1 mb-4"
variants={item}
>
<motion.h1 className=" fw-bold lh-1 mb-4" variants={item}>
Thank You!
</motion.h1>
</div>
@@ -55,7 +52,6 @@ function PortalSuccessPage(props: Props) {
</div>
</div>
</div>
</motion.div>
);
}
@@ -38,9 +38,8 @@ function PricingPage(props: Props) {
const beginStripeFlow = async (
internal_plan_key: string,
analyticsName: string,
analyticsName: string
): Promise<boolean> => {
switch (analyticsName) {
case "plus":
Analytics.premiumSelectPlanPlus();
@@ -64,7 +63,6 @@ function PricingPage(props: Props) {
history.push(signupUrl);
return false;
} else if (props.sessionSubscriptionsWrapper.hasPaidFeatures()) {
Analytics.premiumForwardToStripePortal();
return await beginStripePortalFlow(); // NB: This redirects the user to Stripe
@@ -77,16 +75,19 @@ function PricingPage(props: Props) {
usePrefixedDocumentTitle("Premium Deep Fake TTS");
const environment = FakeYouFrontendEnvironment.getInstance();
const planKey = environment.useProductionStripePlans() ? "production" : "development";
const planKey = environment.useProductionStripePlans()
? "production"
: "development";
const userHasPaidPremium = props.sessionSubscriptionsWrapper.hasPaidFeatures();
const userHasPaidPremium =
props.sessionSubscriptionsWrapper.hasPaidFeatures();
let plusButtonText = "Buy Plus";
let plusButtonDisabled = false;
let proButtonText = "Buy Pro";
let proButtonDisabled = false;
let proBorderCss = "rounded panel p-4 h-100"
let proBorderCss = "rounded panel p-4 h-100";
let eliteButtonText = "Buy Elite";
let eliteButtonDisabled = false;
@@ -122,10 +123,7 @@ function PricingPage(props: Props) {
unsubscribeButton = (
<>
<div className="container pt-5 pb-3 text-center">
<motion.div
className="mt-4"
variants={item}
>
<motion.div className="mt-4" variants={item}>
<button
onClick={() => beginStripeFlow(unsubscribeKey, "unsubscribe")}
className="btn btn-link w-100 fs-6"
@@ -140,30 +138,28 @@ function PricingPage(props: Props) {
// Highlight the mid-tier plan if nothing is subscribed
if (!userHasPaidPremium) {
proBorderCss = "rounded panel p-4 h-100 panel-border"
proBorderCss = "rounded panel p-4 h-100 panel-border";
}
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-3 text-center">
<motion.h1 className="display-5 fw-bold" variants={item}>
<div className="container pt-5 pb-4 text-center">
<motion.h1 className=" fw-bold" variants={item}>
Pricing
</motion.h1>
{/* <p className="fs-5">
By purchasing FakeYou premium, you help us build more!
</p> */}
<motion.div
className="alert alert-warning mt-4 alert-pricing"
className="alert alert-warning mt-4 alert-pricing mb-2"
variants={item}
>
<FontAwesomeIcon icon={faHeart} className="text-red me-3" />
By purchasing FakeYou premium, you help us build more!
</motion.div>
</div>
<div className="container mt-3 mb-5">
<div className="container mb-5">
<div className="row gx-3 gy-4">
{/* Starter Tier */}
{/*<motion.div className="col-12 col-sm-6 col-lg-3" variants={panel}>
<div className="rounded panel p-4 h-100">
@@ -172,7 +168,7 @@ function PricingPage(props: Props) {
</h2>
<h2 className="display-5 fw-bold text-center my-5">
<h2 className=" fw-bold text-center my-5">
${FYP.starter.price}
<span className="fs-5 opacity-75 fw-normal"> /month</span>
</h2>
@@ -224,7 +220,9 @@ function PricingPage(props: Props) {
<div className="rounded panel p-4 h-100">
<h2 className="text-center my-2 fw-bold mb-4">{FYP.plus.tier}</h2>
<button
onClick={() => beginStripeFlow(FYP.plus.internal_plan_key[planKey], "plus")}
onClick={() =>
beginStripeFlow(FYP.plus.internal_plan_key[planKey], "plus")
}
className="btn btn-primary w-100 fs-6"
disabled={plusButtonDisabled}
>
@@ -310,7 +308,7 @@ function PricingPage(props: Props) {
})} */}
</ul>
<hr className="my-4" />
<h6 className="text-center fw-normal">
<h6 className="text-center fw-normal opacity-50">
+ Many more features coming soon!
</h6>
</div>
@@ -321,7 +319,9 @@ function PricingPage(props: Props) {
<div className={proBorderCss}>
<h2 className="text-center my-2 fw-bold mb-4">{FYP.pro.tier}</h2>
<button
onClick={() => beginStripeFlow(FYP.pro.internal_plan_key[planKey], "pro")}
onClick={() =>
beginStripeFlow(FYP.pro.internal_plan_key[planKey], "pro")
}
className="btn btn-primary w-100 fs-6"
disabled={proButtonDisabled}
>
@@ -433,7 +433,7 @@ function PricingPage(props: Props) {
})} */}
</ul>
<hr className="my-4" />
<h6 className="text-center fw-normal">
<h6 className="text-center fw-normal opacity-50">
+ Many more features coming soon!
</h6>
</div>
@@ -446,7 +446,9 @@ function PricingPage(props: Props) {
{FYP.elite.tier}
</h2>
<button
onClick={() => beginStripeFlow(FYP.elite.internal_plan_key[planKey], "elite")}
onClick={() =>
beginStripeFlow(FYP.elite.internal_plan_key[planKey], "elite")
}
className="btn btn-primary w-100 fs-6"
disabled={eliteButtonDisabled}
>
@@ -570,18 +572,16 @@ function PricingPage(props: Props) {
})} */}
</ul>
<hr className="my-4" />
<h6 className="text-center fw-normal">
<h6 className="text-center fw-normal opacity-50">
+ Many more features coming soon!
</h6>
</div>
</motion.div>
</div>
<div className="container mt-3 mb-5">
<div className="row gx-3 gy-4">
{unsubscribeButton}
<div className="container mt-3 mb-5">
<div className="row gx-3 gy-4">{unsubscribeButton}</div>
</div>
</div>
{/* Starter Tier (to show for Latin American countries) */}
{/* <div className="w-100 mt-4">
@@ -111,7 +111,7 @@ function ProfileBanFc(props: Props) {
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-4 px-lg-5 px-xl-3">
<motion.h1 className="display-5 fw-bold mb-3" variants={item}>
<motion.h1 className=" fw-bold mb-3" variants={item}>
Profile &amp; Preferences
</motion.h1>
<motion.div variants={item}>
@@ -236,7 +236,7 @@ function ProfileEditFc(props: Props) {
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-4 px-lg-5 px-xl-3">
<motion.h1 className="display-5 fw-bold mb-3" variants={item}>
<motion.h1 className=" fw-bold mb-3" variants={item}>
Profile &amp; Preferences
</motion.h1>
<motion.div variants={item}>
@@ -364,6 +364,8 @@ function ProfilePage(this: any, props: Props) {
size={150}
username={userData.display_name}
email_hash={userEmailHash}
avatarIndex={userData.default_avatar_index}
backgroundIndex={userData.default_avatar_color_index}
/>
</div>
<div className="border-3 text-center d-lg-none">
@@ -371,6 +373,8 @@ function ProfilePage(this: any, props: Props) {
size={100}
username={userData.display_name}
email_hash={userEmailHash}
avatarIndex={userData.default_avatar_index}
backgroundIndex={userData.default_avatar_color_index}
/>
</div>
</div>
@@ -260,7 +260,7 @@ function TtsEditCategoriesPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container py-5 pb-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
Edit Categories
</motion.h1>
<motion.h4 className="mb-4" variants={item}>
@@ -148,9 +148,7 @@ function TtsModelDeletePage(props: Props) {
let creatorLink = <span />;
if (!!ttsModel?.creator_display_name) {
const creatorUrl = WebUrl.userProfilePage(
ttsModel?.creator_display_name
);
const creatorUrl = WebUrl.userProfilePage(ttsModel?.creator_display_name);
creatorLink = <Link to={creatorUrl}>{ttsModel?.creator_display_name}</Link>;
}
@@ -218,7 +216,7 @@ function TtsModelDeletePage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container py-5 pb-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold mb-3" variants={item}>
<motion.h1 className=" fw-bold mb-3" variants={item}>
{h1Title}
</motion.h1>
<motion.p variants={item}>
@@ -79,10 +79,14 @@ function TtsModelEditPage(props: Props) {
// Moderator-only fields (both observable and editable)
const [useDefaultMFactor, setUseDefaultMFactor] = useState(false);
// NB: The field is an f64, but string is easy to edit
const [maybeCustomMFactor, setMaybeCustomMFactor] = useState<string| null>(null);
const [maybeCustomMFactor, setMaybeCustomMFactor] = useState<string | null>(
null
);
// Vocoder config (modern)
const [maybeCustomVocoderToken, setMaybeCustomVocoderToken] = useState<string | null>(null);
const [maybeCustomVocoderToken, setMaybeCustomVocoderToken] = useState<
string | null
>(null);
// Vocoder config (legacy)
const [defaultPretrainedVocoder, setDefaultPretrainedVocoder] = useState(
@@ -120,12 +124,12 @@ function TtsModelEditPage(props: Props) {
model.maybe_moderator_fields?.use_default_m_factor || false
);
let maybeCustomMFactor = (!!model.maybe_moderator_fields?.maybe_custom_m_factor) ?
model.maybe_moderator_fields?.maybe_custom_m_factor.toString() :
null;
let maybeCustomMFactor = !!model.maybe_moderator_fields
?.maybe_custom_m_factor
? model.maybe_moderator_fields?.maybe_custom_m_factor.toString()
: null;
setMaybeCustomMFactor(maybeCustomMFactor);
} else if (GetTtsModelIsErr(model)) {
switch (model) {
case TtsModelLookupError.NotFound:
@@ -135,7 +139,6 @@ function TtsModelEditPage(props: Props) {
}
}, []);
const listVocoderModels = useCallback(async () => {
const models = await ListVocoderModels();
@@ -144,7 +147,6 @@ function TtsModelEditPage(props: Props) {
}
}, []);
useEffect(() => {
getModel(token);
listVocoderModels();
@@ -192,7 +194,6 @@ function TtsModelEditPage(props: Props) {
setMaybeCustomVocoderToken((ev.target as HTMLSelectElement).value);
};
const handleIsFrontPageFeaturedChange = (
ev: React.FormEvent<HTMLSelectElement>
) => {
@@ -213,12 +214,16 @@ function TtsModelEditPage(props: Props) {
setSuggestedUniqueBotCommand(command);
};
const handleUseDefaultMFactorChange = (ev: React.FormEvent<HTMLInputElement>) => {
const handleUseDefaultMFactorChange = (
ev: React.FormEvent<HTMLInputElement>
) => {
const value = (ev.target as HTMLInputElement).checked;
setUseDefaultMFactor(value);
};
const handleMaybeCustomMFactorChange = (ev: React.FormEvent<HTMLInputElement>) => {
const handleMaybeCustomMFactorChange = (
ev: React.FormEvent<HTMLInputElement>
) => {
ev.preventDefault();
const textValue = (ev.target as HTMLInputElement).value.trim();
setMaybeCustomMFactor(textValue);
@@ -255,7 +260,10 @@ function TtsModelEditPage(props: Props) {
text_pipeline_type: textPipelineType,
};
if (!!maybeCustomVocoderToken && maybeCustomVocoderToken !== UNSET_CUSTOM_VOCODER_SENTINEL) {
if (
!!maybeCustomVocoderToken &&
maybeCustomVocoderToken !== UNSET_CUSTOM_VOCODER_SENTINEL
) {
request.maybe_custom_vocoder_token = maybeCustomVocoderToken;
}
@@ -266,7 +274,8 @@ function TtsModelEditPage(props: Props) {
request.maybe_suggested_unique_bot_command = suggestedUniqueBotCommand;
}
request.use_default_m_factor = useDefaultMFactor;
request.maybe_custom_m_factor = maybeCustomMFactor === null ? null : Number(maybeCustomMFactor.trim());
request.maybe_custom_m_factor =
maybeCustomMFactor === null ? null : Number(maybeCustomMFactor.trim());
}
fetch(endpointUrl, {
@@ -385,33 +394,29 @@ function TtsModelEditPage(props: Props) {
</div>
<div>
<label className="sub-title">
Use m-factoring?
</label>
<label className="sub-title">Use m-factoring?</label>
<div>
<div className="form-group input-icon">
<div className="form-check form-switch">
<input
className="form-check-input"
type="checkbox"
<input
className="form-check-input"
type="checkbox"
onChange={handleUseDefaultMFactorChange}
checked={useDefaultMFactor}
/>
/>
<label className="form-check-label">
Enable this for voices that sound "tinny" or "metalic".
{" "}
This will use a globally defined suggested value (that may change).
Enable this for voices that sound "tinny" or "metalic". This
will use a globally defined suggested value (that may change).
</label>
</div>
</div>
</div>
</div>
<div>
<label className="sub-title">
Use custom m-factor? If set, this will be used instead of the default m-factor.
Use custom m-factor? If set, this will be used instead of the
default m-factor.
</label>
<div className="form-group input-icon">
<FontAwesomeIcon
@@ -427,20 +432,16 @@ function TtsModelEditPage(props: Props) {
/>
</div>
</div>
</>
);
}
let customVocoderOptions = vocoderModels.map((vocoder) => {
let customVocoderOptions = vocoderModels.map((vocoder) => {
return (
<option
key={vocoder.vocoder_token}
value={vocoder.vocoder_token}
>
<option key={vocoder.vocoder_token} value={vocoder.vocoder_token}>
{vocoder.title} (by {vocoder.creator_display_name})
</option>
)
);
});
let usableTextPipelines = isModerator
@@ -468,7 +469,7 @@ function TtsModelEditPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container py-5 pb-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold mb-3" variants={item}>
<motion.h1 className=" fw-bold mb-3" variants={item}>
Edit Model
</motion.h1>
<motion.p variants={item}>
@@ -612,7 +613,9 @@ function TtsModelEditPage(props: Props) {
Public (visible from your profile)
</option>
<option value="hidden">Unlisted (shareable URLs)</option>
<option value="private">Private (work in progress)</option>
<option value="private">
Private (work in progress)
</option>
</select>
</div>
</div>
@@ -40,6 +40,7 @@ export function TtsPageHero(props: Props) {
//"mascot/superbowl-PE.webp",
//"mascot/valentines.webp",
// "mascot/st_patricks.webp",
//"mascot/april_fools.webp",
];
return images[Math.floor(Math.random() * images.length)];
@@ -262,15 +262,14 @@ function TtsModelViewPage(props: Props) {
if (!!ttsModel?.creator_display_name) {
const creatorUrl = WebUrl.userProfilePage(ttsModel?.creator_username);
creatorLink = (
<span>
<div className="d-flex align-items-center gap-2">
<Gravatar
size={15}
size={32}
username={ttsModel.creator_display_name || ""}
email_hash={ttsModel.creator_gravatar_hash || ""}
/>
&nbsp;
<Link to={creatorUrl}>{ttsModel.creator_display_name}</Link>
</span>
</div>
);
}
@@ -586,15 +585,16 @@ function TtsModelViewPage(props: Props) {
<tr>
<th>Vocoder created by</th>
<td>
<Gravatar
size={15}
username={ttsModel.maybe_custom_vocoder.creator_display_name}
email_hash={ttsModel.maybe_custom_vocoder.creator_gravatar_hash}
/>
&nbsp;
<Link to={vocoderCreatorUrl}>
{ttsModel.maybe_custom_vocoder.creator_display_name}
</Link>
<div className="d-flex align-items-center gap-2">
<Gravatar
size={32}
username={ttsModel.maybe_custom_vocoder.creator_display_name}
email_hash={ttsModel.maybe_custom_vocoder.creator_gravatar_hash}
/>
<Link to={vocoderCreatorUrl}>
{ttsModel.maybe_custom_vocoder.creator_display_name}
</Link>
</div>
</td>
</tr>
</>
@@ -705,7 +705,7 @@ function TtsModelViewPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container py-5">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold mb-3" variants={item}>
<motion.h1 className="fw-bold mb-3" variants={item}>
{title}
</motion.h1>
{/* Rate Voice Model Buttons */}
@@ -133,9 +133,7 @@ function TtsResultDeletePage(props: Props) {
return <div />; // Exit rendering until data loads.
}
const modelLink = WebUrl.ttsModelPage(
ttsInferenceResult.tts_model_token
);
const modelLink = WebUrl.ttsModelPage(ttsInferenceResult.tts_model_token);
let durationSeconds = ttsInferenceResult?.duration_millis / 1000;
let modelName = ttsInferenceResult.tts_model_title;
@@ -189,7 +187,7 @@ function TtsResultDeletePage(props: Props) {
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-4 px-lg-5 px-xl-3">
<motion.h1 className="display-5 fw-bold mb-3" variants={item}>
<motion.h1 className=" fw-bold mb-3" variants={item}>
{h1Title}
</motion.h1>
<motion.div variants={item}>
@@ -152,7 +152,7 @@ function TtsResultEditPage(props: Props) {
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-4 px-lg-5 px-xl-3">
<motion.h1 className="display-5 fw-bold mb-3" variants={item}>
<motion.h1 className=" fw-bold mb-3" variants={item}>
Edit Result Visibility
</motion.h1>
<motion.div variants={item}>
@@ -219,17 +219,16 @@ function TtsResultViewPage(props: Props) {
if (!!ttsInferenceResult.maybe_creator_user_token) {
let creatorLink = `/profile/${ttsInferenceResult.maybe_creator_username}`;
creatorDetails = (
<span>
<div className="d-flex align-items-center gap-2">
<Gravatar
size={15}
size={32}
username={ttsInferenceResult.maybe_creator_display_name || ""}
email_hash={ttsInferenceResult.maybe_creator_gravatar_hash || ""}
/>
&nbsp;
<Link to={creatorLink}>
{ttsInferenceResult.maybe_creator_display_name}
</Link>
</span>
</div>
);
}
@@ -237,19 +236,18 @@ function TtsResultViewPage(props: Props) {
if (!!ttsInferenceResult.maybe_model_creator_user_token) {
let modelCreatorLink = `/profile/${ttsInferenceResult.maybe_model_creator_username}`;
modelCreatorDetails = (
<span>
<div className="d-flex align-items-center gap-2">
<Gravatar
size={15}
size={32}
username={ttsInferenceResult.maybe_model_creator_display_name || ""}
email_hash={
ttsInferenceResult.maybe_model_creator_gravatar_hash || ""
}
/>
&nbsp;
<Link to={modelCreatorLink}>
{ttsInferenceResult.maybe_model_creator_display_name}
</Link>
</span>
</div>
);
}
@@ -434,7 +432,7 @@ function TtsResultViewPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container py-5">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold mb-2" variants={item}>
<motion.h1 className=" fw-bold mb-2" variants={item}>
{headingTitle}
</motion.h1>
{/* Rate Result Buttons */}
@@ -113,7 +113,7 @@ function UploadTtsModelPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-4 px-md-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
Upload Voice (TTS Model)
</motion.h1>
<motion.h4 variants={item}>This works just like YouTube!</motion.h4>
@@ -1,7 +1,10 @@
import React, { useState } from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { DiscordLink } from "@storyteller/components/src/elements/DiscordLink";
import { EnqueueRetrieval, EnqueueRetrievalIsOk } from "@storyteller/components/src/api/retrieval/EnqueueRetrieval";
import {
EnqueueRetrieval,
EnqueueRetrievalIsOk,
} from "@storyteller/components/src/api/retrieval/EnqueueRetrieval";
import { useHistory } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
import { BackLink } from "../../_common/BackLink";
@@ -69,7 +72,7 @@ function UploadVocoderPage(props: Props) {
idempotency_token: idempotencyToken,
title: title,
download_url: downloadUrl,
generic_download_type: 'hifigan',
generic_download_type: "hifigan",
};
const response = await EnqueueRetrieval(request);
@@ -86,7 +89,7 @@ function UploadVocoderPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-4 px-md-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
Upload Vocoder Model
</motion.h1>
<motion.h4 variants={item}>Make your voice sound better!</motion.h4>
@@ -113,9 +116,9 @@ function UploadVocoderPage(props: Props) {
</p>
<p>
Once your vocoder is successfully uploaded, you'll be able to
add it to your voice models. Others will be able to use it too, and
you'll get credited.
Once your vocoder is successfully uploaded, you'll be able to add it
to your voice models. Others will be able to use it too, and you'll
get credited.
</p>
{/* TODO TEMP (2022-03-08) <p>
@@ -130,7 +133,8 @@ function UploadVocoderPage(props: Props) {
<div className="d-flex flex-column gap-4">
<div>
<label className="sub-title">
Vocoder Title, eg. "High Pitched Voices (trained on Powerpuff Girls, MLP, and various anime characters)"
Vocoder Title, eg. "High Pitched Voices (trained on Powerpuff
Girls, MLP, and various anime characters)"
</label>
<div className="form-group">
<input
@@ -163,7 +167,6 @@ function UploadVocoderPage(props: Props) {
<p className="help">{downloadUrlInvalidReason}</p>
</div>
</div>
</div>
</div>
@@ -94,7 +94,7 @@ function UploadW2lPhotoPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-3 px-md-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
Upload Photo (w2l template)
</motion.h1>
<motion.div className="my-3" variants={item}>
@@ -94,7 +94,7 @@ function UploadW2lVideoPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-3 px-md-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
Upload Video (w2l template)
</motion.h1>
<motion.div className="my-3" variants={item}>
@@ -106,7 +106,7 @@ function W2lResultDeletePage(props: Props) {
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-4 px-md-4 px-lg-5 px-xl-3">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
{h1Title}
</motion.h1>
<motion.div className="pt-3" variants={item}>
@@ -148,7 +148,7 @@ function W2lResultEditPage(props: Props) {
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pb-4 pt-5 px-md-4 px-lg-5 px-xl-3">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
Edit Result Visibility
</motion.h1>
<motion.div className="pt-3" variants={item}>
@@ -264,7 +264,7 @@ function W2lResultViewPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container py-5 px-md-4 px-lg-5 px-xl-3">
<motion.h1
className="display-5 fw-bold text-center text-lg-start"
className=" fw-bold text-center text-lg-start"
variants={item}
>
Lipsync Result
@@ -121,7 +121,7 @@ function W2lTemplateApprovePage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
{h1Title}
</motion.h1>
</div>
@@ -172,7 +172,7 @@ function W2lTemplateDeletePage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
{h1Title}
</motion.h1>
</div>
@@ -121,7 +121,7 @@ function W2lTemplateEditPage(props: Props) {
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<h1 className="display-5 fw-bold">Edit Template</h1>
<h1 className=" fw-bold">Edit Template</h1>
</div>
<div className="mt-3">
<BackLink link={templateLink} text="Back to template" />
@@ -412,7 +412,7 @@ function W2lTemplateViewPage(props: Props) {
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container py-5 px-md-4 px-lg-5 px-xl-3">
<motion.h1 className="display-5 fw-bold" variants={item}>
<motion.h1 className=" fw-bold" variants={item}>
Video Lip Sync Template
</motion.h1>
<motion.h4 variants={item}>Template: {w2lTemplate?.title}</motion.h4>
@@ -92,7 +92,7 @@ function IndexPage(props: Props) {
<h6 className="pre-heading text-center fw-bold pt-2">Features</h6>
<img src="assets/title-shape.png" alt="" />
</div>
<h1 className="display-5 fw-bold mt-3">
<h1 className=" fw-bold mt-3">
<span className="word">TTS Features</span>
</h1>
<div className="features-section">
@@ -166,7 +166,7 @@ function IndexPage(props: Props) {
<h6 className="pre-heading text-center fw-bold pt-2">TTS Voices</h6>
<img src="assets/title-shape.png" alt="" />
</div>
<h1 className="display-5 fw-bold mt-3">
<h1 className=" fw-bold mt-3">
<span className="word">Voice Previews</span>
</h1>
<div className="voices-section">
@@ -206,7 +206,7 @@ function IndexPage(props: Props) {
<h6 className="pre-heading fw-bold pt-2">Statistics</h6>
<img src="assets/title-shape.png" alt="" />
</div>
<h1 className="display-5 fw-bold mb-4 mt-3">
<h1 className=" fw-bold mb-4 mt-3">
<span className="word">Our Insights</span>
</h1>
<p>
@@ -249,10 +249,10 @@ function IndexPage(props: Props) {
</h6>
<img src="assets/title-shape.png" alt="" />
</div>
<h1 className="display-5 fw-bold mt-3 text-center d-none d-lg-block">
<h1 className=" fw-bold mt-3 text-center d-none d-lg-block">
<span className="word">Frequently Asked Questions</span>
</h1>
<h1 className="display-5 fw-bold mt-3 text-center d-lg-none">
<h1 className=" fw-bold mt-3 text-center d-lg-none">
<span className="word">FAQ</span>
</h1>
@@ -362,7 +362,7 @@ function IndexPage(props: Props) {
</h6>
<img src="assets/title-shape.png" alt="" />
</div>
<h1 className="display-5 fw-bold mb-4 mt-3">
<h1 className=" fw-bold mb-4 mt-3">
<span className="word">Join Our Community</span>
</h1>
<p className="lead text-center mb-5">
@@ -388,7 +388,7 @@ function IndexPage(props: Props) {
<img className="cta-img w-100" src="assets/cta-img.png" alt="" />
</div>
<div className="col-md-6 cta-right text-center text-sm-start px-5">
<h1 className="display-5 fw-bold mb-5">
<h1 className=" fw-bold mb-5">
Get started with Storyteller Stream!
</h1>
<div className="d-flex gap-3">
@@ -1,5 +1,4 @@
import { GitSha } from "@storyteller/components/src/elements/GitSha";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { Link } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
+2 -3
View File
@@ -1,6 +1,5 @@
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
interface Props {
sessionWrapper: SessionWrapper;
@@ -26,7 +25,7 @@ function TopNav(props: Props) {
aria-label="Offcanvas navbar large"
>
<div className="container">
<a className="navbar-brand me-5 pr-8" href="#">
<a className="navbar-brand me-5 pr-8" href="#/">
<img
src="/assets/powerstream-logo.png"
alt="PowerStream Logo"
@@ -48,7 +47,7 @@ function TopNav(props: Props) {
aria-labelledby="offcanvasNavbar2Label"
>
<div className="offcanvas-header" data-bs-toggle="offcanvas">
<a className="navbar-brand me-5 pr-8" href="#">
<a className="navbar-brand me-5 pr-8" href="#/">
<img
src="/assets/powerstream-logo.png"
alt="PowerStream Logo"
@@ -95,7 +95,7 @@ function IndexPage(props: Props) {
<h6 className="pre-heading text-center fw-bold pt-2">Features</h6>
<img src="assets/title-shape.png" alt="" />
</div>
<h1 className="display-5 fw-bold mt-3">
<h1 className=" fw-bold mt-3">
<span className="word">TTS Features</span>
</h1>
<div className="features-section">
@@ -175,7 +175,7 @@ function IndexPage(props: Props) {
<h6 className="pre-heading text-center fw-bold pt-2">TTS Voices</h6>
<img src="assets/title-shape.png" alt="" />
</div>
<h1 className="display-5 fw-bold mt-3">
<h1 className=" fw-bold mt-3">
<span className="word">Voice Previews</span>
</h1>
<div className="voices-section">
@@ -215,7 +215,7 @@ function IndexPage(props: Props) {
<h6 className="pre-heading fw-bold pt-2">It's Obvious</h6>
<img src="assets/title-shape.png" alt="" />
</div>
<h1 className="display-5 fw-bold mb-4 mt-3">
<h1 className=" fw-bold mb-4 mt-3">
<span className="word">Better Streams Grow</span>
</h1>
<p>
@@ -258,10 +258,10 @@ function IndexPage(props: Props) {
</h6>
<img src="assets/title-shape.png" alt="" />
</div>
<h1 className="display-5 fw-bold mt-3 text-center d-none d-lg-block">
<h1 className=" fw-bold mt-3 text-center d-none d-lg-block">
<span className="word">Frequently Asked Questions</span>
</h1>
<h1 className="display-5 fw-bold mt-3 text-center d-lg-none">
<h1 className=" fw-bold mt-3 text-center d-lg-none">
<span className="word">FAQ</span>
</h1>
@@ -362,7 +362,7 @@ function IndexPage(props: Props) {
</h6>
<img src="assets/title-shape.png" alt="" />
</div>
<h1 className="display-5 fw-bold mb-4 mt-3">
<h1 className=" fw-bold mb-4 mt-3">
<span className="word">Join Our Community</span>
</h1>
<p className="lead text-center mb-5">
@@ -404,9 +404,7 @@ function IndexPage(props: Props) {
/>
</div>
<div className="col-md-6 cta-right text-center text-sm-start px-4 px-lg-5">
<h1 className="display-5 fw-bold mb-5">
Get started with Power Stream!
</h1>
<h1 className=" fw-bold mb-5">Get started with Power Stream!</h1>
<div className="d-flex flex-column flex-sm-row gap-3">
<a
className="btn btn-cta w-100"
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

@@ -230,7 +230,7 @@ function IndexPage() {
</div>
<div className="container mt-5 pt-5">
<div className="row gx-5 flex-row-reverse gy-4">
<div className="col-lg-6 img-layers">
<div className="col-lg-6 img-layers d-none d-xl-block">
<Tilt trackOnWindow={true} tiltReverse={true}>
<img
src="/images/screenshots/fakeyou-screen.webp"
@@ -244,8 +244,16 @@ function IndexPage() {
className="img-front-right img-mascot"
/>
</div>
<div className="col-lg-6 d-xl-none">
<img
src="/images/screenshots/fakeyou-screen-mobile.webp"
alt="FakeYou"
className="img-fluid"
/>
</div>
<div className="col-lg-6 d-flex flex-column justify-content-center">
<h1 className="fw-bold display-5">FakeYou</h1>
<h1 className="fw-bold ">FakeYou</h1>
<h4 className="fw-normal opacity-75 mb-4">
Used by millions of people every month
</h4>
@@ -275,7 +283,7 @@ function IndexPage() {
<div className="bg-dark section-2">
<div className="container">
<div className="row gx-5 gy-5">
<div className="col-lg-6 img-layers">
<div className="col-lg-6 img-layers d-none d-xl-block">
<Tilt trackOnWindow={true} tiltReverse={true}>
<img
src="/images/screenshots/powerstream-screen.webp"
@@ -289,8 +297,16 @@ function IndexPage() {
className="img-front-left img-mascot"
/>
</div>
<div className="col-lg-6 d-xl-none">
<img
src="/images/screenshots/powerstream-screen-mobile.webp"
alt="FakeYou"
className="img-fluid"
/>
</div>
<div className="col-lg-6 d-flex flex-column justify-content-center">
<h1 className="fw-bold display-5">PowerStream</h1>
<h1 className="fw-bold ">PowerStream</h1>
<h4 className="fw-normal opacity-75 mb-4">
Twitch Streamers and Creators can engage and monetize
</h4>
@@ -720,27 +736,18 @@ function IndexPage() {
</Marquee>
</div>
<div className="container py-5 mt-5">
<div className="row g-4 g-md-5 align-items-center">
<div className="col-12 col-md-6">
<div>
<h1 className="fw-bold display-6 about-title">
We're an interdiciplinary team building the one-person cloud
studio
</h1>
<hr className="p-1 mt-4 text-red opacity-100 w-25" />
</div>
</div>
<div className="col-12 col-md-6">
<p className="fw-normal opacity-75">
Our dream is to empower anyone to create full feature-length
content without institutional capital, large teams, huge amounts
of time, or a deep reservoir of highly specialized talent. We
want to empower everyone to turn their dreams into physical
form.
</p>
</div>
</div>
<div className="container py-5 text-center d-flex flex-column align-items-center mt-3">
<h1 className="fw-bold about-title mt-5">
We’re building the future production cloud studio
</h1>
<p className="fw-normal opacity-75 mt-4">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec quis
vehicula odio. Nulla non sem scelerisque, hendrerit libero sagittis,
rhoncus orci. Etiam in molestie augue. Donec lobortis tincidunt
massa, a posuere libero sagittis eget. Vivamus sagittis mauris non
tristique ornare. Pellentesque rhoncus ipsum magna, nec lacinia odio
sagittis et. Nunc aliquet cursus dui fermentum molestie.
</p>
</div>
<div className="container mt-md-5">
@@ -16,12 +16,12 @@ function Footer(props: Props) {
</p>*/}
<h2>Want more Donald Trump TTS?</h2>
<p>
The best <a href="https://fakeyou.com">Donald Trump TTS</a> can be found
The best <a href="https://fakeyou.com/character/donald-trump">Donald Trump TTS</a> can be found
at FakeYou.
</p>
<p>
FakeYou is the world's best Text to Speech website and has thousands of
voices. Angry Donald J Trump, Sad DJ Trump, Hillary Clinton, Nixon, and
voices. Angry <a href="https://fakeyou.com/character/donald-trump">Donald J Trump AI Voice</a>, Sad DJ Trump, Hillary Clinton, Nixon, and
so many others!
</p>
<p>
+1 -3
View File
@@ -128,9 +128,7 @@
</div>
<div class="col-lg-6 px-md-2 px-lg-5 px-xl-2">
<div class="ani1 load-hidden text-center text-lg-start">
<h1 class="display-5 fw-bold lh-1 mb-4">
A Glimpse of the Future
</h1>
<h1 class="fw-bold lh-1 mb-4">A Glimpse of the Future</h1>
<p class="lead">
We're building FakeYou as just one component of a broad set of
production and creative tooling.
+1 -3
View File
@@ -128,9 +128,7 @@
</div>
<div class="col-lg-6 px-md-2 px-lg-5 px-xl-2">
<div class="ani1 load-hidden text-center text-lg-start">
<h1 class="display-5 fw-bold lh-1">
Professionally Clone Your Voice
</h1>
<h1 class="fw-bold lh-1">Professionally Clone Your Voice</h1>
<h3 class="mb-4">(or any voice) for just $70</h3>
<div class="d-flex flex-column justify-content-center">
<h3>Want a Custom Voice You Can Use?</h3>
+1 -1
View File
@@ -116,7 +116,7 @@
<div class="container py-5">
<div class="d-flex flex-column">
<h1 class="display-5 fw-bold">Contribute to FakeYou!</h1>
<h1 class="fw-bold">Contribute to FakeYou!</h1>
<h3 class="mb-4">You make FakeYou better by contributing</h3>
<p class="lead">
You'll get credited for everything you contribute. You'll also get
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff. Show more