mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-11 20:32:12 +00:00
landing demo update
This commit is contained in:
1 parent
533023211e
commit
b762c6fcda
6 files changed
+235
-73
No files matched your search
@@ -10,7 +10,7 @@ export default function SelectionBubbles({
|
||||
options,
|
||||
onSelect,
|
||||
}: SelectionBubbleProps) {
|
||||
//Select first one as default
|
||||
//Select first one as defaults
|
||||
const [selectedOption, setSelectedOption] = useState<string | null>(
|
||||
options.length > 0 ? options[0] : null
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapp
|
||||
import Alert from "components/common/Alert/Alert";
|
||||
import { useLocalize } from "hooks";
|
||||
import React from "react";
|
||||
import LandingDemo from "./FakeYouLandingDemo";
|
||||
import LandingDemo from "./LandingDemo/FakeYouLandingDemo";
|
||||
import {
|
||||
FrontendInferenceJobType,
|
||||
InferenceJob,
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import Wavesurfer from "react-wavesurfer.js";
|
||||
|
||||
interface Props {
|
||||
filename: string;
|
||||
play: boolean;
|
||||
onFinish?: () => void;
|
||||
}
|
||||
|
||||
export default function DemoTtsAudioPlayer(props: Props) {
|
||||
const [position, setPosition] = useState(0);
|
||||
|
||||
const handleFinish = useCallback(() => {
|
||||
setPosition(0);
|
||||
props.onFinish && props.onFinish();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.onFinish]);
|
||||
|
||||
return (
|
||||
<div className="flex-grow-1 h-100 overflow-hidden">
|
||||
<Wavesurfer
|
||||
key={props.filename}
|
||||
onFinish={handleFinish}
|
||||
pos={position}
|
||||
src={props.filename || ""}
|
||||
height={36}
|
||||
progressColor="#fc8481"
|
||||
waveColor="#b09e9e"
|
||||
cursorColor="#fc8481"
|
||||
playing={props.play}
|
||||
responsive={true}
|
||||
normalize={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+98
-71
@@ -1,13 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { faDeleteLeft, faPlay } from "@fortawesome/pro-solid-svg-icons";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { faPause, faPlay, faShuffle } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import {
|
||||
Panel,
|
||||
Button,
|
||||
TextArea,
|
||||
SelectionBubbles,
|
||||
Accordion,
|
||||
} from "components/common";
|
||||
import { Panel, Button, TextArea, SelectionBubbles } from "components/common";
|
||||
import {
|
||||
FrontendInferenceJobType,
|
||||
InferenceJob,
|
||||
@@ -21,8 +15,14 @@ import {
|
||||
GenerateTtsAudioIsOk,
|
||||
} from "@storyteller/components/src/api/tts/GenerateTtsAudio";
|
||||
import { Analytics } from "common/Analytics";
|
||||
import { Link } from "react-router-dom";
|
||||
import { SessionTtsInferenceResultList } from "v2/view/_common/SessionTtsInferenceResultsList";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import DemoTtsAudioPlayer from "./DemoAudioPlayer";
|
||||
import {
|
||||
GetTtsInferenceJobStatus,
|
||||
GetTtsInferenceJobStatusIsOk,
|
||||
} from "@storyteller/components/src/api/jobs/GetTtsInferenceJobStatus";
|
||||
import RandomTexts from "./RandomTexts";
|
||||
import "./LandingDemo.scss";
|
||||
|
||||
interface TtsInferencePanelProps {
|
||||
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
|
||||
@@ -45,23 +45,29 @@ export default function LandingDemo({
|
||||
inferenceJobsByCategory,
|
||||
}: TtsInferencePanelProps) {
|
||||
const [textBuffer, setTextBuffer] = useState("");
|
||||
const [isEnqueuing, setIsEnqueuing] = useState(false);
|
||||
const [maybeTtsError, setMaybeTtsError] = useState<
|
||||
GenerateTtsAudioErrorType | undefined
|
||||
>(undefined);
|
||||
const [isAudioLimitAlertVisible, setAudioLimitAlertVisible] = useState(false);
|
||||
const [currentAudioUrl, setCurrentAudioUrl] = useState<string | null>(null);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [jobToken, setJobToken] = useState<string | null>(null);
|
||||
const [lastEnqueuedText, setLastEnqueuedText] = useState<string | null>(null);
|
||||
const [isAudioLoading, setIsAudioLoading] = useState(false);
|
||||
const [lastSelectedVoice, setLastSelectedVoice] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const handleChangeText = (ev: React.FormEvent<HTMLTextAreaElement>) => {
|
||||
const textValue = (ev.target as HTMLTextAreaElement).value;
|
||||
setTextBuffer(textValue);
|
||||
setAudioLimitAlertVisible(textValue.length > 100);
|
||||
};
|
||||
|
||||
//Set voices here
|
||||
const voiceModelTokenMap: { [key: string]: string } = {
|
||||
Spongebob: "weight_vrx7j407cxk45jenkrd769h9b",
|
||||
Trump: "trump_model_token",
|
||||
Messi: "messi_model_token",
|
||||
// Add more voice options and their modelTokens as needed
|
||||
Rick: "weight_0f762jdzgsy1dhpb86qxy4ssm",
|
||||
Mickey: "weight_sfyjyr67ag1647xs0r7gmvkks",
|
||||
Eric: "weight_h8ebh6fyjyrr1vsjregw6yz8y",
|
||||
"Angry Male": "weight_hehgvegadf08mfp5rzd69dmh4",
|
||||
};
|
||||
|
||||
const [voiceToken, setVoiceToken] = useState(voiceModelTokenMap["Spongebob"]);
|
||||
@@ -78,7 +84,14 @@ export default function LandingDemo({
|
||||
return false;
|
||||
}
|
||||
|
||||
setIsEnqueuing(true);
|
||||
// Check if the text hasn't changed and the voice hasn't changed
|
||||
if (textBuffer === lastEnqueuedText && voiceToken === lastSelectedVoice) {
|
||||
setIsPlaying(!isPlaying);
|
||||
setIsAudioLoading(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
setIsAudioLoading(true);
|
||||
|
||||
const modelToken = voiceToken;
|
||||
|
||||
@@ -103,15 +116,52 @@ export default function LandingDemo({
|
||||
} else {
|
||||
enqueueTtsJob(response.inference_job_token);
|
||||
}
|
||||
// Store the job token
|
||||
setJobToken(response.inference_job_token);
|
||||
// Store the last enqueued text
|
||||
setLastEnqueuedText(textBuffer);
|
||||
// Store the last selected voice
|
||||
setLastSelectedVoice(voiceToken);
|
||||
} else if (GenerateTtsAudioIsError(response)) {
|
||||
setMaybeTtsError(response.error);
|
||||
}
|
||||
|
||||
setIsEnqueuing(false);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!jobToken) return;
|
||||
|
||||
const fetch = async () => {
|
||||
const jobStatusResponse = await GetTtsInferenceJobStatus(jobToken);
|
||||
|
||||
if (GetTtsInferenceJobStatusIsOk(jobStatusResponse)) {
|
||||
const job = inferenceJobs.find(job => job.jobToken === jobToken);
|
||||
if (job && jobStatusResponse.state.maybe_public_bucket_wav_audio_path) {
|
||||
const audioLink = new BucketConfig().getGcsUrl(
|
||||
jobStatusResponse.state.maybe_public_bucket_wav_audio_path
|
||||
);
|
||||
setCurrentAudioUrl(audioLink);
|
||||
|
||||
if (audioLink !== currentAudioUrl) {
|
||||
setIsAudioLoading(false);
|
||||
setIsPlaying(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetch();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [jobToken, inferenceJobs, isPlaying]);
|
||||
|
||||
const handleAudioFinish = () => {
|
||||
setIsPlaying(false);
|
||||
};
|
||||
|
||||
const voiceOptions = Object.keys(voiceModelTokenMap);
|
||||
|
||||
// Show errors on TTS failure
|
||||
let maybeError = <></>;
|
||||
if (!!maybeTtsError) {
|
||||
let hasMessage = false;
|
||||
@@ -119,7 +169,9 @@ export default function LandingDemo({
|
||||
switch (maybeTtsError) {
|
||||
case GenerateTtsAudioErrorType.TooManyRequests:
|
||||
hasMessage = true;
|
||||
message = <>Error: Too many requests. Please try again in a bit.</>;
|
||||
message = (
|
||||
<>Too many requests! Please wait a few minutes then try again.</>
|
||||
);
|
||||
break;
|
||||
case GenerateTtsAudioErrorType.ServerError |
|
||||
GenerateTtsAudioErrorType.BadRequest |
|
||||
@@ -145,33 +197,17 @@ export default function LandingDemo({
|
||||
}
|
||||
}
|
||||
|
||||
let audioLimitAlert = <></>;
|
||||
if (
|
||||
isAudioLimitAlertVisible &&
|
||||
!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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const voiceOptions = Object.keys(voiceModelTokenMap);
|
||||
const generateRandomText = () => {
|
||||
const randomIndex = Math.floor(Math.random() * RandomTexts.length);
|
||||
const randomText = RandomTexts[randomIndex];
|
||||
setTextBuffer(randomText);
|
||||
};
|
||||
|
||||
return (
|
||||
<Panel padding={true}>
|
||||
<form className="d-flex flex-column">
|
||||
<div>
|
||||
<div>
|
||||
<div className="d-flex">
|
||||
<label className="sub-title flex-grow-1">Select a Voice</label>
|
||||
</div>
|
||||
<SelectionBubbles
|
||||
@@ -181,8 +217,15 @@ export default function LandingDemo({
|
||||
</div>
|
||||
|
||||
<div className="d-flex flex-column mt-3">
|
||||
<div>
|
||||
<label className="sub-title flex-grow-1">Your Text</label>
|
||||
<div className="d-flex justify-content-center pb-2">
|
||||
<label className="sub-title flex-grow-1 pb-0">Your Text</label>
|
||||
<Button
|
||||
icon={faShuffle}
|
||||
label="Randomize Text"
|
||||
onClick={generateRandomText}
|
||||
variant="link"
|
||||
className="fs-7 randomize-text-button"
|
||||
/>
|
||||
</div>
|
||||
<TextArea
|
||||
placeholder="Enter the text you want the voice to say here..."
|
||||
@@ -191,41 +234,25 @@ export default function LandingDemo({
|
||||
rows={4}
|
||||
resize={false}
|
||||
/>
|
||||
{audioLimitAlert}
|
||||
</div>
|
||||
|
||||
{maybeError}
|
||||
|
||||
<div className="d-flex gap-3 align-items-center mt-4">
|
||||
<Button
|
||||
icon={faPlay}
|
||||
icon={isPlaying ? faPause : faPlay}
|
||||
square={true}
|
||||
onClick={handleEnqueueTts}
|
||||
isLoading={isEnqueuing}
|
||||
isLoading={isAudioLoading}
|
||||
disabled={textBuffer.length === 0}
|
||||
/>
|
||||
<>Autoplaying audio player here</>
|
||||
<DemoTtsAudioPlayer
|
||||
filename={currentAudioUrl || ""}
|
||||
play={isPlaying}
|
||||
onFinish={handleAudioFinish}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{inferenceJobs[0] && (
|
||||
<div className="mt-4">
|
||||
<Accordion>
|
||||
<Accordion.Item title="Session TTS Results" defaultOpen={true}>
|
||||
<div className="p-3">
|
||||
<SessionTtsInferenceResultList
|
||||
inferenceJobs={
|
||||
inferenceJobsByCategory.get(
|
||||
FrontendInferenceJobType.TextToSpeech
|
||||
)!
|
||||
}
|
||||
ttsInferenceJobs={ttsInferenceJobs}
|
||||
sessionSubscriptionsWrapper={sessionSubscriptionsWrapper}
|
||||
/>
|
||||
</div>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
{maybeError}
|
||||
</div>
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.randomize-text-button {
|
||||
color: desaturate($primary, 30%) !important;
|
||||
opacity: 1;
|
||||
|
||||
&:hover {
|
||||
color: desaturate($primary, 10%) !important;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
const RandomTexts = [
|
||||
"Hello, world!",
|
||||
"Welcome to the future.",
|
||||
"The quick brown fox jumps over the lazy dog.",
|
||||
"In a hole in the ground there lived a hobbit.",
|
||||
"Once upon a time in a land far, far away.",
|
||||
"Can't believe you've done this.",
|
||||
"Sometimes, I wish I was an octopus, so I could slap eight people at once.",
|
||||
"Explaining why one is right is not arguing, it's just clarifying.",
|
||||
"Wallets are like onions; opening them brings tears.",
|
||||
"I'm not lazy, I'm on energy-saving mode.",
|
||||
"When life gives you melons, dyslexia might be involved.",
|
||||
"Atoms are untrustworthy; they make up everything.",
|
||||
"Glue history books are impossible to put down.",
|
||||
"Time travel jokes are funny in any era, but you might have missed it.",
|
||||
"I would lose weight, but I hate losing.",
|
||||
"A messy room is just an open-air museum.",
|
||||
"Trying to fail and succeeding is a real conundrum.",
|
||||
"Procrastination is just engaging in numerous side quests.",
|
||||
"Being early for tomorrow is a new kind of punctuality.",
|
||||
"Adulthood is the longest test run that never ends.",
|
||||
"Beauty sleep's advertising is misleading.",
|
||||
"I'm on a whiskey diet. I've lost three days already.",
|
||||
"Magical beds remember all forgotten tasks.",
|
||||
"Pillows are the unsung heroes of hairstyling.",
|
||||
"I'm not saying I'm Batman, but have you ever seen me and Batman in the same room?",
|
||||
"Couches are the battlegrounds for avoiding problems.",
|
||||
"Life's brevity demands self-laughter or outsourcing it.",
|
||||
"Being a limited edition is far from weird.",
|
||||
"Office chairs that spin make the job worth it.",
|
||||
"Wake-up time defines personhood, not the time of day.",
|
||||
"Math problems multiply in silence.",
|
||||
"Physical conservatism is the new fitness trend.",
|
||||
"Extra fries misunderstood as exercise is a common error.",
|
||||
"I'm fluent in three languages: English, sarcasm, and profanity.",
|
||||
"Multitasking involves listening, ignoring, and forgetting simultaneously.",
|
||||
"Indecisiveness is a choice, sometimes.",
|
||||
"I'm really good at stuff until people watch me do that stuff.",
|
||||
"Correctness needs no argument, just explanation.",
|
||||
"Coffee relationships are the most committed ones.",
|
||||
"Rush hour is the greatest paradox.",
|
||||
"I'm not bossy. I just have better ideas.",
|
||||
"Silence and duct tape both have their merits.",
|
||||
"I'm not procrastinating. I'm under a lot of pressure and very well could be a diamond soon.",
|
||||
"Battery saver mode is essential for work avoidance.",
|
||||
"Exploration is just being lost with style.",
|
||||
"Pressing harder on a remote control seems like a valid effort when batteries are weak.",
|
||||
"I'm convinced that bugs in software are just features in disguise.",
|
||||
"Adulthood is getting excited about a new sponge for the kitchen, I've accepted this.",
|
||||
"Life's too short, I always smile while I still have all my teeth.",
|
||||
"In video games, I've learned that encountering enemies often means you're going in the right direction.",
|
||||
"I'm often on the hunt for motivation, but it's like searching for a 404 error.",
|
||||
"Debating with a computer, I've realized, is as effective as trying to solve algebra by chewing bubblegum.",
|
||||
"During rush hour, I remind myself that patience is indeed a virtue.",
|
||||
"I transformed a shy friend into a social butterfly, and now I wonder what I've unleashed.",
|
||||
"The quest for sleep, sanity, and a peaceful hobbit life is something I'm still on.",
|
||||
"My bed has a magical ability to remind me of forgotten tasks at night.",
|
||||
"When the Wi-Fi goes down, I discover family members are actually nice people.",
|
||||
"A cookie in each hand, I believe, is the true definition of a balanced diet.",
|
||||
"Why use Google when I'm convinced someone believes they have all the answers?",
|
||||
"Exploring the great indoors, I've found, is because outside is overrated.",
|
||||
"I'm fluent in three languages: English, sarcasm, and late-night coding.",
|
||||
"Why wait for a time machine when I have memories and music?",
|
||||
"The best way to avoid chores, I've found, is to pretend not to know they exist.",
|
||||
"If I can't find the sunshine, I've learned to be the person who switches on the light.",
|
||||
"Realizing too late that adulthood is a trap is a lesson I've learned the hard way.",
|
||||
"The art of being wise, I've discovered, is knowing what to overlook, like unread emails.",
|
||||
"Why call it fast food if I have to wait in line for it?",
|
||||
"I've discovered that my spirit animal is a sloth on weekdays.",
|
||||
"Making a 'quick' phone call, I've realized, is never quick.",
|
||||
"The mystery of how one sock always disappears in the laundry remains unsolved, I've noticed.",
|
||||
"Finding out that my emergency contact is a pizza delivery service was a surprise.",
|
||||
"The realization that autocorrect has become my worst enema... I mean, enemy, is frustrating.",
|
||||
"Wondering why my coffee tastes like I should have made it myself is a daily ritual.",
|
||||
"The silent joy of canceling plans is an underrated pleasure I cherish.",
|
||||
"Discovering that my daily workout is trying to dodge spoilers online is a modern struggle.",
|
||||
"Realizing that my phone's battery life lasts longer than my motivation is a harsh reality.",
|
||||
"The paradox of being a perfectionist procrastinator is a real struggle I face.",
|
||||
"Finding comfort in the fact that chocolate understands me is a sweet relief.",
|
||||
"Realizing I'm not late, I'm just on a creative time schedule is how I cope.",
|
||||
"Discovering that my hobby is collecting dust on my gym membership was an eye-opener.",
|
||||
"The irony of being a tech expert but unable to fix my sleep schedule is not lost on me.",
|
||||
"Understanding that my to-do list is more of a suggestion than a requirement helps me sleep at night.",
|
||||
"The thrill of solving a bug in code is the coder's version of a plot twist, I've found.",
|
||||
"Realizing that my idea of a balanced diet is having a piece of tech in both hands is a modern dilemma.",
|
||||
];
|
||||
|
||||
export default RandomTexts;
|
||||
Reference in new issue
Block a user