Merge branch 'master' into bflat-fy-voice-designer-use-voice

This commit is contained in:
BFlat authored and GitHub committed 2023-11-07 14:06:41 +07:00
commit 55bb967fad
32 files changed
+809 -203

No files matched your search

+1
View File
@@ -36,6 +36,7 @@
},
"dependencies": {
"@react-spring/types": "^9.7.3",
"universal-cookie": "^6.1.1",
"yarn": "^1.22.19"
},
"packageManager": "yarn@3.3.1"
@@ -1,15 +1,15 @@
import MakeRequest from "../../MakeRequest";
export interface DreateVoiceRequest {
export interface DeleteVoiceRequest {
set_delete: boolean;
as_mod: boolean;
}
export interface DreateVoiceResponse {
export interface DeleteVoiceResponse {
success: boolean
}
export const DeleteVoice = MakeRequest<string, DreateVoiceRequest, DreateVoiceResponse>({
export const DeleteDataset = MakeRequest<string, DeleteVoiceRequest, DeleteVoiceResponse>({
method: "DELETE",
routingFunction: (voiceToken: string) => `/v1/voice_designer/dataset/${ voiceToken }/delete`,
});
@@ -3,7 +3,7 @@ import { UserDetailsLight } from "../../_common/UserDetailsLight";
export interface ListDatasetsByUserRequest {}
export interface ListDatasetsByUserResponse {
export interface Dataset {
dataset_token: string,
title: string,
@@ -17,6 +17,11 @@ export interface ListDatasetsByUserResponse {
updated_at: Date,
}
export interface ListDatasetsByUserResponse {
success: boolean,
datasets: Dataset[],
}
export const ListDatasetsByUser = MakeRequest<string, ListDatasetsByUserRequest, ListDatasetsByUserResponse>({
method: "GET",
routingFunction: (userName: string) => `/v1/voice_designer/dataset/user/${ userName }/list`,
@@ -1,4 +1,9 @@
import { ActiveSubscription, ListActiveSubscriptions, ListActiveSubscriptionsIsSuccess, ListActiveSubscriptionsSuccessResponse } from "../api/premium/ListActiveSubscriptions";
import {
ActiveSubscription,
ListActiveSubscriptions,
ListActiveSubscriptionsIsSuccess,
ListActiveSubscriptionsSuccessResponse,
} from "../api/premium/ListActiveSubscriptions";
import { FakeYouFrontendEnvironment } from "../env/FakeYouFrontendEnvironment";
const FAKEYOU_NAMESPACE = "fakeyou";
@@ -72,8 +77,25 @@ export class SessionSubscriptionsWrapper {
return maybePlan !== undefined;
}
private findActiveSubscription(namespace: string, product_slug: string) : ActiveSubscription | undefined {
const subs = this.listActiveSubscriptionResponse?.active_subscriptions || [];
return subs.find((sub) => sub.namespace === namespace && sub.product_slug === product_slug);
public getActiveProductSlug(): string | undefined {
if (this.hasActiveEliteSubscription()) {
return "fakeyou_elite";
} else if (this.hasActiveProSubscription()) {
return "fakeyou_pro";
} else if (this.hasActivePlusSubscription()) {
return "fakeyou_plus";
}
return undefined;
}
}
private findActiveSubscription(
namespace: string,
product_slug: string
): ActiveSubscription | undefined {
const subs =
this.listActiveSubscriptionResponse?.active_subscriptions || [];
return subs.find(
(sub) => sub.namespace === namespace && sub.product_slug === product_slug
);
}
}
+124 -103
View File
@@ -3,6 +3,7 @@ import "scss/custom-bootstrap.scss";
import React from "react";
import { ApiConfig } from "@storyteller/components";
import Cookies from 'universal-cookie';
import {
DetectLocale,
DetectLocaleIsOk,
@@ -58,7 +59,7 @@ import { VoiceConversionModelUploadJob } from "@storyteller/components/src/jobs/
import { VoiceConversionModelListItem } from "@storyteller/components/src/api/voice_conversion/ListVoiceConversionModels";
import HttpBackend from "i18next-http-backend";
import { InferenceJobs } from "components/providers";
import { InferenceJobs, Session as SessionProvider } from "components/providers";
// NB: We're transitioning over to this instance of i18n-next that loads translations over HTTP from Json Files.
// The old i18n-next instance (see below) bakes in translations into the compiled javascript blob.
@@ -256,6 +257,7 @@ class App extends React.Component<Props, State> {
voiceConversionModels: [],
maybeSelectedVoiceConversionModel: undefined,
};
}
componentWillMount() {
@@ -293,13 +295,18 @@ class App extends React.Component<Props, State> {
querySession = async () => {
const sessionWrapper = await SessionWrapper.lookupSession();
const username = sessionWrapper.getDisplayName();
const cookies = new Cookies();
if (username !== undefined) {
// Track only logged-in users (for now)
PosthogClient.enablePosthog();
PosthogClient.setUsername(username);
window.dataLayer.push({
'user_id': username,
});
cookies.set('logged_in_username', username, { path: '/', expires: new Date(Date.now()+3 * 86400000) });
window.dataLayer.push({
'user_id': username,
});
} else {
cookies.remove('logged_in_username', { path: '/' });
}
this.setState({
sessionWrapper: sessionWrapper,
@@ -307,8 +314,18 @@ class App extends React.Component<Props, State> {
};
querySessionSubscriptions = async () => {
const cookies = new Cookies();
const sessionSubscriptionsWrapper =
await SessionSubscriptionsWrapper.lookupActiveSubscriptions();
await SessionSubscriptionsWrapper.lookupActiveSubscriptions();
const plan = sessionSubscriptionsWrapper.getActiveProductSlug();
if (plan !== undefined) {
cookies.set('logged_in_user_plan', plan, { path: '/', expires: new Date(Date.now()+3 * 86400000) });
} else {
cookies.remove('logged_in_user_plan', { path: '/' });
}
this.setState({
sessionSubscriptionsWrapper: sessionSubscriptionsWrapper,
});
@@ -789,104 +806,108 @@ class App extends React.Component<Props, State> {
*/}
<div className="migrationComponentWrapper">
<InferenceJobs {...{ byCategory: this.state.inferenceJobsByCategory }}>
<Switch>
<Route path="/">
<PageContainer
sessionWrapper={this.state.sessionWrapper}
querySessionAction={this.querySession}
sessionSubscriptionsWrapper={
this.state.sessionSubscriptionsWrapper
}
querySessionSubscriptionsAction={
this.querySessionSubscriptions
}
isShowingVocodesNotice={this.state.isShowingVocodesNotice}
clearVocodesNotice={this.clearVocodesNotice}
isShowingLangaugeNotice={this.state.isShowingLanguageNotice}
clearLanguageNotice={this.clearLanguageNotice}
displayLanguage={this.state.displayLanguage}
primaryLanguageCode={this.state.primaryLanguageCode}
isShowingTwitchTtsNotice={
this.state.isShowingTwitchTtsNotice
}
clearTwitchTtsNotice={this.clearTwitchTtsNotice}
isShowingPleaseFollowNotice={
this.state.isShowingPleaseFollowNotice
}
clearPleaseFollowNotice={this.clearPleaseFollowNotice}
isShowingBootstrapLanguageNotice={
this.state.isShowingBootstrapLanguageNotice
}
clearBootstrapLanguageNotice={
this.clearBootstrapLanguageNotice
}
enqueueInferenceJob={this.enqueueInferenceJob}
inferenceJobs={this.state.inferenceJobs}
inferenceJobsByCategory={this.state.inferenceJobsByCategory}
enqueueTtsJob={this.enqueueTtsJob}
ttsInferenceJobs={this.state.ttsInferenceJobs}
enqueueW2lJob={this.enqueueW2lJob}
w2lInferenceJobs={this.state.w2lInferenceJobs}
enqueueTtsModelUploadJob={this.enqueueTtsModelUploadJob}
ttsModelUploadJobs={this.state.ttsModelUploadJobs}
enqueueW2lTemplateUploadJob={
this.enqueueW2lTemplateUploadJob
}
w2lTemplateUploadJobs={this.state.w2lTemplateUploadJobs}
enqueueVocoderUploadJob={this.enqueueVocoderUploadJob}
vocoderUploadJobs={this.state.vocoderUploadJobs}
enqueueVoiceConversionModelUploadJob={
this.enqueueVoiceConversionModelUploadJob
}
voiceConversionModelUploadJobs={
this.state.voiceConversionModelUploadJobs
}
textBuffer={this.state.textBuffer}
setTextBuffer={this.setTextBuffer}
clearTextBuffer={this.clearTextBuffer}
ttsModels={this.props.allTtsModels}
setTtsModels={this.props.setAllTtsModels}
allTtsCategories={this.props.allTtsCategories}
setAllTtsCategories={this.props.setAllTtsCategories}
computedTtsCategoryAssignments={
this.props.computedTtsCategoryAssignments
}
setComputedTtsCategoryAssignments={
this.props.setComputedTtsCategoryAssignments
}
allTtsCategoriesByTokenMap={
this.props.allTtsCategoriesByTokenMap
}
allTtsModelsByTokenMap={this.props.allTtsModelsByTokenMap}
ttsModelsByCategoryToken={
this.props.ttsModelsByCategoryToken
}
dropdownCategories={this.props.dropdownCategories}
setDropdownCategories={this.props.setDropdownCategories}
selectedCategories={this.props.selectedCategories}
setSelectedCategories={this.props.setSelectedCategories}
maybeSelectedTtsModel={this.props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={
this.props.setMaybeSelectedTtsModel
}
selectedTtsLanguageScope={
this.props.selectedTtsLanguageScope
}
setSelectedTtsLanguageScope={
this.props.setSelectedTtsLanguageScope
}
voiceConversionModels={this.state.voiceConversionModels}
setVoiceConversionModels={this.setAllVoiceConversionModels}
maybeSelectedVoiceConversionModel={
this.state.maybeSelectedVoiceConversionModel
}
setMaybeSelectedVoiceConversionModel={
this.setMaybeSelectedVoiceConversionModel
}
/>
</Route>
</Switch>
<InferenceJobs {...{
byCategory: this.state.inferenceJobsByCategory
}}>
<SessionProvider {...{ sessionWrapper: this.state.sessionWrapper }}>
<Switch>
<Route path="/">
<PageContainer
sessionWrapper={this.state.sessionWrapper}
querySessionAction={this.querySession}
sessionSubscriptionsWrapper={
this.state.sessionSubscriptionsWrapper
}
querySessionSubscriptionsAction={
this.querySessionSubscriptions
}
isShowingVocodesNotice={this.state.isShowingVocodesNotice}
clearVocodesNotice={this.clearVocodesNotice}
isShowingLangaugeNotice={this.state.isShowingLanguageNotice}
clearLanguageNotice={this.clearLanguageNotice}
displayLanguage={this.state.displayLanguage}
primaryLanguageCode={this.state.primaryLanguageCode}
isShowingTwitchTtsNotice={
this.state.isShowingTwitchTtsNotice
}
clearTwitchTtsNotice={this.clearTwitchTtsNotice}
isShowingPleaseFollowNotice={
this.state.isShowingPleaseFollowNotice
}
clearPleaseFollowNotice={this.clearPleaseFollowNotice}
isShowingBootstrapLanguageNotice={
this.state.isShowingBootstrapLanguageNotice
}
clearBootstrapLanguageNotice={
this.clearBootstrapLanguageNotice
}
enqueueInferenceJob={this.enqueueInferenceJob}
inferenceJobs={this.state.inferenceJobs}
inferenceJobsByCategory={this.state.inferenceJobsByCategory}
enqueueTtsJob={this.enqueueTtsJob}
ttsInferenceJobs={this.state.ttsInferenceJobs}
enqueueW2lJob={this.enqueueW2lJob}
w2lInferenceJobs={this.state.w2lInferenceJobs}
enqueueTtsModelUploadJob={this.enqueueTtsModelUploadJob}
ttsModelUploadJobs={this.state.ttsModelUploadJobs}
enqueueW2lTemplateUploadJob={
this.enqueueW2lTemplateUploadJob
}
w2lTemplateUploadJobs={this.state.w2lTemplateUploadJobs}
enqueueVocoderUploadJob={this.enqueueVocoderUploadJob}
vocoderUploadJobs={this.state.vocoderUploadJobs}
enqueueVoiceConversionModelUploadJob={
this.enqueueVoiceConversionModelUploadJob
}
voiceConversionModelUploadJobs={
this.state.voiceConversionModelUploadJobs
}
textBuffer={this.state.textBuffer}
setTextBuffer={this.setTextBuffer}
clearTextBuffer={this.clearTextBuffer}
ttsModels={this.props.allTtsModels}
setTtsModels={this.props.setAllTtsModels}
allTtsCategories={this.props.allTtsCategories}
setAllTtsCategories={this.props.setAllTtsCategories}
computedTtsCategoryAssignments={
this.props.computedTtsCategoryAssignments
}
setComputedTtsCategoryAssignments={
this.props.setComputedTtsCategoryAssignments
}
allTtsCategoriesByTokenMap={
this.props.allTtsCategoriesByTokenMap
}
allTtsModelsByTokenMap={this.props.allTtsModelsByTokenMap}
ttsModelsByCategoryToken={
this.props.ttsModelsByCategoryToken
}
dropdownCategories={this.props.dropdownCategories}
setDropdownCategories={this.props.setDropdownCategories}
selectedCategories={this.props.selectedCategories}
setSelectedCategories={this.props.setSelectedCategories}
maybeSelectedTtsModel={this.props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={
this.props.setMaybeSelectedTtsModel
}
selectedTtsLanguageScope={
this.props.selectedTtsLanguageScope
}
setSelectedTtsLanguageScope={
this.props.setSelectedTtsLanguageScope
}
voiceConversionModels={this.state.voiceConversionModels}
setVoiceConversionModels={this.setAllVoiceConversionModels}
maybeSelectedVoiceConversionModel={
this.state.maybeSelectedVoiceConversionModel
}
setMaybeSelectedVoiceConversionModel={
this.setMaybeSelectedVoiceConversionModel
}
/>
</Route>
</Switch>
</SessionProvider>
</InferenceJobs>
</div>
</div>
@@ -20,6 +20,7 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
iconFlip?: boolean;
download?: boolean | string;
disabled?: boolean;
isLoading?: boolean;
}
export default function Button({
@@ -36,6 +37,7 @@ export default function Button({
iconFlip = false,
download,
disabled,
isLoading = false,
...rest
}: ButtonProps) {
let iconMarginClass = !square && label ? (iconFlip ? "ms-2" : "me-2") : "";
@@ -45,13 +47,22 @@ export default function Button({
let LabelComponent = !square ? label : null;
const SpinnerComponent = isLoading ? (
<div
className="spinner-border spinner-border-sm text-white ms-2"
role="status"
>
<span className="visually-hidden">Loading...</span>
</div>
) : null;
const externalClass = rest.className || "";
const commonProps = {
className: `${externalClass} button ${small ? "button-small" : ""} ${
square ? (small ? "button-square-small" : "button-square") : ""
} button-${variant} ${full ? "w-100" : ""}`,
disabled: disabled,
disabled: disabled || isLoading,
};
delete rest.className;
@@ -60,11 +71,13 @@ export default function Button({
<>
{LabelComponent}
{IconComponent}
{SpinnerComponent}
</>
) : (
<>
{IconComponent}
{LabelComponent}
{SpinnerComponent}
</>
);
@@ -4,14 +4,18 @@ import { faTrashAlt } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useSpring, a } from "@react-spring/web";
interface ModalProps {
show: boolean;
handleClose: () => void;
onCancel?: (e: React.MouseEvent<HTMLElement>) => any;
onConfirm?: (e: React.MouseEvent<HTMLElement>) => any;
title: string;
content: React.ReactNode;
}
const Modal: React.FC<ModalProps> = ({ show, handleClose, title, content }) => {
const Modal: React.FC<ModalProps> = ({ show, handleClose, onCancel: cancelEvent, onConfirm: confirmEvent, title, content }) => {
const modalRef = useRef<HTMLDivElement>(null);
const fadeIn = useSpring({
@@ -22,6 +26,16 @@ const Modal: React.FC<ModalProps> = ({ show, handleClose, title, content }) => {
},
});
const onCancel = (e: React.MouseEvent<HTMLElement>) => {
if (cancelEvent) cancelEvent(e);
handleClose();
};
const onConfirm = (e: React.MouseEvent<HTMLElement>) => {
if (confirmEvent) confirmEvent(e);
handleClose();
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
@@ -70,9 +84,9 @@ const Modal: React.FC<ModalProps> = ({ show, handleClose, title, content }) => {
<Button
variant="secondary"
label="Cancel"
onClick={handleClose}
onClick={onCancel}
/>
<Button variant="danger" label="Delete" onClick={handleClose} />
<Button variant="danger" label="Delete" onClick={onConfirm} />
</div>
</div>
</div>
@@ -8,9 +8,12 @@ import FileDetails from "./FileDetails";
import FileWrapper from "./FileWrapper";
import FileLabel from "./FileLabel";
import ImageInput from "./ImageInput";
import Input from "./Input";
import NumberSlider from "./NumberSlider";
import Panel from "./Panel";
import Radio from "./Radio";
import SegmentButtons from "./SegmentButtons";
import Select from "./Select";
import Slider from "./Slider";
import Spinner from "./Spinner";
@@ -25,9 +28,12 @@ export {
FileWrapper,
FileLabel,
ImageInput,
Input,
NumberSlider,
Panel,
Radio,
SegmentButtons,
Select,
Slider,
Spinner
};
@@ -5,7 +5,13 @@ import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import Button from "components/common/Button";
import BackButton from "components/common/BackButton";
interface BackConfig {
label: string;
to: string;
};
interface PageHeaderProps {
back?: BackConfig;
titleIcon?: IconDefinition;
title: string | React.ReactNode;
subText: string | React.ReactNode;
@@ -25,6 +31,7 @@ interface PageHeaderProps {
}
export default function PageHeader({
back,
titleIcon,
title,
subText,
@@ -50,13 +57,17 @@ export default function PageHeader({
return (
<div className="py-3 py-lg-4">
<Panel clear={true}>
{showBackButton && (
{( back || showBackButton) && (
<div className="d-flex mb-2 mb-lg-3">
<BackButton label={backbuttonLabel} to={backbuttonTo} />
<BackButton label={back ? back.label : backbuttonLabel} to={ back ? back.to : backbuttonTo } />
</div>
)}
<div className="row">
<div className="d-flex flex-column col-lg-7 justify-content-center gap-4 py-3">
<div
className={`d-flex flex-column ${
imageUrl ? "col-lg-7" : "col-12"
} justify-content-center gap-4 py-2`}
>
<div>
<h1 className="fw-bold">
{/* {icon} */}
@@ -76,7 +87,11 @@ export default function PageHeader({
</div>
)}
</div>
<div className="d-none d-lg-block col-lg-5">
<div
className={`d-none col-lg-5 ${
imageUrl ? "d-lg-block" : "d-lg-none"
}`}
>
{imageUrl && (
<img
src={imageUrl}
@@ -6,9 +6,10 @@ import { InferenceJobsContext } from 'context';
interface Props {
children?: any;
byCategory?: any;
sessionWrapper?: any;
}
export default function InferenceJobs({ children, byCategory }: Props) {
export default function InferenceJobs({ children, byCategory, sessionWrapper }: Props) {
const processStatus = (job: InferenceJob) => {
switch (job.jobState) {
case JobState.PENDING:
@@ -21,7 +22,7 @@ export default function InferenceJobs({ children, byCategory }: Props) {
default: return -1;
}
};
return <InferenceJobsContext.Provider {...{ value: { byCategory, processStatus } }}>
return <InferenceJobsContext.Provider {...{ value: { byCategory, processStatus, sessionWrapper } }}>
{ children }
</InferenceJobsContext.Provider>
};
@@ -0,0 +1,13 @@
import React from 'react';
import { SessionContext } from 'context';
interface Props {
children?: any;
sessionWrapper?: any;
}
export default function Session({ children, sessionWrapper }: Props) {
return <SessionContext.Provider {...{ value: { sessionWrapper } }}>
{ children }
</SessionContext.Provider>
};
@@ -0,0 +1 @@
export { default } from './Session';
@@ -1,3 +1,4 @@
import InferenceJobs from "./InferenceJobs";
import Session from "./Session";
export { InferenceJobs };
export { InferenceJobs, Session };
@@ -0,0 +1,7 @@
import { createContext } from "react";
interface SessionContextType {
sessionWrapper?: any
}
export default createContext<SessionContextType>({});
+2 -1
View File
@@ -1,3 +1,4 @@
import InferenceJobsContext from "./InferenceJobsContext";
import SessionContext from "./SessionContext";
export { InferenceJobsContext };
export { InferenceJobsContext, SessionContext };
+2 -1
View File
@@ -4,6 +4,7 @@ import useId from './useId';
import useIdempotency from './useIdempotency';
import useInferenceJobs from './useInferenceJobs';
import useLocalize from './useLocalize';
import useSession from './useSession';
import useVideo from './useVideo';
export { useFile, useHover, useId, useIdempotency, useInferenceJobs, useLocalize, useVideo };
export { useFile, useHover, useId, useIdempotency, useInferenceJobs, useLocalize, useSession, useVideo };
@@ -0,0 +1 @@
export { default } from './useSession';
@@ -0,0 +1,7 @@
import { useContext } from 'react';
import { SessionContext } from 'context';
export default function useInferenceJobs() {
const { sessionWrapper } = useContext(SessionContext);
return sessionWrapper?.sessionStateResponse || {};
};
@@ -82,6 +82,7 @@ import { VoiceConversionModelListItem } from "@storyteller/components/src/api/vo
import { CommunityCommissionsPage } from "./pages/contest/CommunityCommissionsPage";
import { ProductUsageInfoPage } from "./pages/product_usage_info/ProductUsageInfoPage";
import { VoiceDesignerFormPage } from "./pages/voice_designer/VoiceDesignerFormPage";
import DatasetEditor from "./pages/voice_designer/DatasetEditor";
import { GenerateSpeechPage } from "./pages/generate_speech/GenerateSpeechPage";
import VcModelViewPage from "./pages/vc/vc_model_view/VcModelViewPage";
import VcModelEditPage from "./pages/vc/vc_model_edit/VcModelEditPage";
@@ -94,6 +95,9 @@ import MediaPage from "./pages/media/MediaPage";
import { VoiceDesignerMainPage } from "./pages/voice_designer/VoiceDesignerMainPage";
import { VoiceDesignerVoiceEditPage } from "./pages/voice_designer/VoiceDesignerVoiceEditPage";
import VoiceDesignerUseVoicePage from "./pages/voice_designer/VoiceDesignerUseVoicePage";
import { PasswordResetEmailPage } from "./pages/password_reset/PasswordResetEmailPage";
import { PasswordResetVerificationPage } from "./pages/password_reset/PasswordResetVerificationPage";
import { PasswordResetChangePage } from "./pages/password_reset/PasswordResetChangePage";
interface Props {
sessionWrapper: SessionWrapper;
@@ -270,6 +274,24 @@ class PageContainer extends React.Component<
/>
</Route>
<Route path="/password-reset/new">
<PasswordResetChangePage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/password-reset/verify">
<PasswordResetVerificationPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/password-reset">
<PasswordResetEmailPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/profile/:username/edit">
<ProfileEditFc sessionWrapper={this.props.sessionWrapper} />
</Route>
@@ -631,7 +653,10 @@ class PageContainer extends React.Component<
<Route
exact
path="/voice-designer/dataset/:dataset_token/edit"
component={VoiceDesignerFormPage}
{...{
sessionWrapper: this.props.sessionWrapper
}}
component={DatasetEditor}
/>
{/* Route for handling dataset token for uploading samples */}
@@ -655,7 +680,7 @@ class PageContainer extends React.Component<
</Route>
<Route path="/voice-designer">
<VoiceDesignerMainPage />
<VoiceDesignerMainPage/>
</Route>
<Route path="/generate-speech">
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import { faMicrophone, faVolume } from "@fortawesome/pro-solid-svg-icons";
import { faVolume } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
import { PageHeaderWithImage } from "v2/view/_common/PageHeaderWithImage";
@@ -1,5 +1,4 @@
import React, { useState } from "react";
import { WebUrl } from "../../../../common/WebUrl";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { Link, useHistory } from "react-router-dom";
import {
@@ -13,6 +12,9 @@ import { faUser, faKey } from "@fortawesome/free-solid-svg-icons";
import { Analytics } from "../../../../common/Analytics";
import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle";
import { PosthogClient } from "@storyteller/components/src/analytics/PosthogClient";
import Container from "components/common/Container";
import Panel from "components/common/Panel";
import PageHeader from "components/layout/PageHeader";
interface Props {
sessionWrapper: SessionWrapper;
@@ -88,62 +90,59 @@ function LoginPage(props: Props) {
}
return (
<div className="pt-lg-5">
<div className="container-panel pb-5 pt-lg-5 login-panel">
<div className="panel p-3 p-lg-4 load-hidden mt-5 mt-lg-0 px-md-4">
<h1 className="panel-title fw-bold">Login</h1>
<div className="py-6">
{errorWarning}
<Container type="panel" className="login-panel">
<PageHeader
title="Login"
subText="Log into your account."
panel={false}
/>
<Panel padding={true}>
{errorWarning}
<form onSubmit={handleFormSubmit}>
<div className="d-flex flex-column gap-4">
<div>
<label className="sub-title">Username or Email</label>
<div className="form-group input-icon">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faUser} />
</span>
<input
className="form-control"
type="text"
placeholder="Username or Email"
value={usernameOrEmail}
onChange={handleUsernameOrEmailChange}
/>
</div>
{/*<p className="help"></p>*/}
</div>
<div>
<label className="sub-title">Password</label>
<div className="form-group input-icon">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faKey} />
</span>
<input
className="form-control"
type="password"
placeholder="Password"
value={password}
onChange={handlePasswordChange}
/>
</div>
{/*<p className="help"></p>*/}
</div>
<button className="btn btn-primary w-100 mt-2">Login</button>
<p>
Don’t have an account? &nbsp;
<Link to={WebUrl.signupPage()} className="text-link">
Create an account now.
</Link>
</p>
<form onSubmit={handleFormSubmit}>
<div className="d-flex flex-column gap-4">
<div>
<label className="sub-title">Username or Email</label>
<div className="form-group input-icon">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faUser} />
</span>
<input
className="form-control"
type="text"
placeholder="Username or Email"
value={usernameOrEmail}
onChange={handleUsernameOrEmailChange}
/>
</div>
</form>
{/*<p className="help"></p>*/}
</div>
<div>
<label className="sub-title">Password</label>
<div className="form-group input-icon">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faKey} />
</span>
<input
className="form-control"
type="password"
placeholder="Password"
value={password}
onChange={handlePasswordChange}
/>
</div>
<p className="d-flex flex-column flex-lg-row gap-2">
<Link to="/password-reset" className="text-link form-text">
Forgot your password?
</Link>
</p>
</div>
</div>
</div>
</div>
</div>
<button className="btn btn-primary w-100 mt-4">Login</button>
</form>
</Panel>
</Container>
);
}
@@ -0,0 +1,80 @@
import React, { useState } from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useHistory } from "react-router-dom";
import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle";
import Container from "components/common/Container";
import PageHeader from "components/layout/PageHeader";
import Input from "components/common/Input";
import { Button } from "components/common";
import Panel from "components/common/Panel";
import { faKey } from "@fortawesome/pro-solid-svg-icons";
import BackButton from "components/common/BackButton";
interface Props {
sessionWrapper: SessionWrapper;
}
function PasswordResetChangePage(props: Props) {
let history = useHistory();
const [isPasswordChanged, setIsPasswordChanged] = useState(false);
const [isLoading, setIsLoading] = useState(false);
if (props.sessionWrapper.isLoggedIn()) {
history.push("/");
}
usePrefixedDocumentTitle("Reset Password");
const handlePasswordChange = async () => {
// Simulate a delay using a timer (dummy timer)
setIsLoading(true);
setTimeout(() => {
setIsPasswordChanged(true);
setIsLoading(false);
}, 2000);
};
return (
<Container type="panel" className="login-panel">
<PageHeader
title="Change Password"
subText="Set your new password."
panel={false}
/>
<Panel padding={true}>
{isPasswordChanged ? (
<div className="d-flex flex-column gap-3 align-items-start">
<p>Your password has been successfully changed!</p>
<BackButton label="Back to Login" to="/login" />
</div>
) : (
<form>
<div className="d-flex flex-column gap-4">
<Input
label="New Password"
icon={faKey}
placeholder="Enter new password"
/>
<Input
label="Confirm New Password"
icon={faKey}
placeholder="Confirm new password"
/>
<Button
label="Confirm Change"
onClick={handlePasswordChange}
isLoading={isLoading}
/>
</div>
</form>
)}
</Panel>
</Container>
);
}
export { PasswordResetChangePage };
@@ -0,0 +1,52 @@
import React from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useHistory } from "react-router-dom";
import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle";
import Container from "components/common/Container";
import PageHeader from "components/layout/PageHeader";
import Input from "components/common/Input";
import { Button } from "components/common";
import Panel from "components/common/Panel";
import { faEnvelope } from "@fortawesome/pro-solid-svg-icons";
interface Props {
sessionWrapper: SessionWrapper;
}
function PasswordResetEmailPage(props: Props) {
let history = useHistory();
if (props.sessionWrapper.isLoggedIn()) {
history.push("/");
}
usePrefixedDocumentTitle("Reset Password");
return (
<Container type="panel" className="login-panel">
<PageHeader
title="Reset Password"
subText="Enter your account's email address you'd like your password
reset information sent to."
panel={false}
showBackButton={true}
/>
<Panel padding={true}>
<form>
<div className="d-flex flex-column gap-4">
<Input
label="Enter Email"
icon={faEnvelope}
placeholder="Email address"
/>
<Button label="Reset Password" onClick={() => {}} />
</div>
</form>
</Panel>
</Container>
);
}
export { PasswordResetEmailPage };
@@ -0,0 +1,64 @@
import React, { useState } from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useHistory } from "react-router-dom";
import { usePrefixedDocumentTitle } from "../../../../common/UsePrefixedDocumentTitle";
import Container from "components/common/Container";
import PageHeader from "components/layout/PageHeader";
import Input from "components/common/Input";
import { Button } from "components/common";
import Panel from "components/common/Panel";
import { faLock } from "@fortawesome/pro-solid-svg-icons";
interface Props {
sessionWrapper: SessionWrapper;
}
function PasswordResetVerificationPage(props: Props) {
let history = useHistory();
const [isLoading, setIsLoading] = useState(false);
if (props.sessionWrapper.isLoggedIn()) {
history.push("/");
}
usePrefixedDocumentTitle("Code Verification");
const handleVerifyCode = async () => {
// Simulate a delay using a timer (dummy timer)
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 2000);
};
return (
<Container type="panel" className="login-panel">
<PageHeader
title="Code Verification"
subText="Enter the 6-digit code sent to your email address."
panel={false}
/>
<Panel padding={true}>
<form>
<div className="d-flex flex-column gap-4">
<Input
label="Verification Code"
icon={faLock}
placeholder="Enter 6-digit code"
/>
<Button
label="Verify Code"
onClick={handleVerifyCode}
isLoading={isLoading}
/>
</div>
</form>
</Panel>
</Container>
);
}
export { PasswordResetVerificationPage };
@@ -0,0 +1,83 @@
import React, { useEffect, useState } from 'react';
import { useParams } from "react-router-dom";
import Container from "components/common/Container";
import PageHeader from "components/layout/PageHeader";
// import { Stepper } from "./components/Stepper";
// import { StepperControls } from "./components/StepperControls";
import { faPencil, faWaveform } from "@fortawesome/pro-solid-svg-icons";
import { Input, Panel, Select } from "components/common";
import useVoiceRequests from "./useVoiceRequests";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
interface Props {
value?: any;
sessionWrapper: SessionWrapper;
}
interface RouteParams {
dataset_token?: string;
}
export default function DatasetEditor({ sessionWrapper, value }: Props) {
const { datasets, datasetByToken } = useVoiceRequests();
const [title, titleSet] = useState("");
const [language,languageSet] = useState({ value: "en", label: "English"});
const [visibility,visibilitySet] = useState("");
const [ready,readySet] = useState(false);
// const [currentStep, currentStepSet] = useState(0);
const { dataset_token: token } = useParams<RouteParams>();
const pageTitle = token ? `Edit` : "Create Dataset";
const subText = token ?
"Edit your dataset by uploading more samples to create a new voice" :
"Add voice details and upload audio samples to clone your voice!";
const titleIcon = token ? faPencil : faWaveform;
const back = {
label: "Back to Voice Designer",
to: token ? "/voice-designer/datasets" : "/voice-designer/voices"
};
// const steps = ["A","B"];
useEffect(() => {
if (token && datasets && datasets.length && !ready) {
const { creator_set_visibility, ietf_language_tag, title: resTitle } = datasetByToken(token);
console.log("☔️",ietf_language_tag);
readySet(true);
titleSet(resTitle);
languageSet({ value: ietf_language_tag, label: "asss" });
visibilitySet(creator_set_visibility);
}
},[datasetByToken,datasets,ready,token]);
const inputCtrl = (todo: any) => ({ target }: { target: any}) => todo(target.value);
const selectChange = ({ value }: any) => { // react-select doesn't format events like events, doesn't pass name prop, ew
console.log("🍕",value);
// languageSet(value);
}
const options = [
{ value: "en", label: "English" },
{ value: "es", label: "Spanish" },
{ value: "fr", label: "French" },
];
const [abc,abcSet] = useState("123");
console.log("🚜", visibility, language, title);
return <Container type="panel">
<PageHeader {...{ back, subText, title: pageTitle, titleIcon }} panel={false} />
<Panel>
<div className="p-3 py-4 p-md-4">
<Input {...{ label: "Title", onChange: inputCtrl(titleSet), value: title }}/>
<Select {...{ label: "Language", name: "language", onChange: selectChange, options, value: language }}/>
<select {...{ name: 'abc', value: abc, onChange: (e:any) => console.log("🌸",e.target.value) }}>
<option value="123">123</option>
<option value="456">456</option>
<option value="789">789</option>
</select>
</div>
</Panel>
</Container>;
};
@@ -1,15 +1,25 @@
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { faPlus, faWaveform } from "@fortawesome/pro-solid-svg-icons";
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
// import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
import Panel from "components/common/Panel";
import PageHeader from "components/layout/PageHeader";
import Container from "components/common/Container";
import { Route, NavLink, Switch, Redirect } from "react-router-dom";
import ListItems from "./components/ListItems/ListItems";
import ListItems from "./components/NewList";
import Modal from "components/common/Modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { faMicrophone } from "@fortawesome/pro-solid-svg-icons";
import useVoiceRequests from "./useVoiceRequests";
// interface Props {
// sessionWrapper: SessionWrapper;
// }
function VoiceDesignerMainPage() {
usePrefixedDocumentTitle("Voice Designer");
const { datasets, deleteDataSet } = useVoiceRequests();
const [isDeleteVoiceModalOpen, setIsDeleteVoiceModalOpen] = useState(false);
const [isDeleteDatasetModalOpen, setIsDeleteDatasetModalOpen] =
@@ -31,24 +41,51 @@ function VoiceDesignerMainPage() {
setIsDeleteDatasetModalOpen(false);
};
const voicesList = [
const DataBadge = () => <span className="dataset-badge mb-0">Dataset</span>;
const VoiceBadge = () => <FontAwesomeIcon icon={faMicrophone} className="me-2 me-lg-3" />;
const history = useHistory();
const navToEdit = (token: string) => { history.push(`/voice-designer/dataset/${token}/edit`) }
const rowClick = (todo: any) => ({ target }: { target: any }) => {
let datasetToken = datasets[target.name.split(",")[0].split(":")[1]].dataset_token;
todo(datasetToken);
};
const actionDataSets = datasets.map((dataset,i) => {
return {
...dataset,
badge: DataBadge,
buttons: [{
label: "Edit",
small: true,
variant: "secondary",
onClick: rowClick(navToEdit)
},{
label: "Delete",
small: true,
variant: "danger",
onClick: rowClick(deleteDataSet)
}],
name: dataset.title
};
});
const voicesList = [
{
badge: VoiceBadge,
name: "Donald Trump (45th US President)",
modelToken: "TM:z7x37sbvb8vc",
isCreating: true,
},
{
badge: VoiceBadge,
name: "Spongebob Squarepants (Season 1)",
modelToken: "TM:z7x37sbvb8vc",
},
];
const datasetList = [
{
name: "Donald Trump (45th US President)",
modelToken: "dummyToken",
},
];
// const emptyList = [] as any[];
function MyVoices() {
return (
@@ -64,7 +101,8 @@ function VoiceDesignerMainPage() {
return (
<ListItems
type="dataset"
data={datasetList}
data={actionDataSets}
//data={datasetList}
handleDeleteDataset={openDeleteDatasetModal}
/>
);
@@ -0,0 +1,74 @@
import React from 'react';
import Button from "components/common/Button";
import { faPlus } from "@fortawesome/pro-solid-svg-icons";
import "./ListItems/ListItems.scss";
interface ListItemsProps {
data: any[];
type: "voice" | "dataset";
handleDeleteVoice?: () => void;
handleDeleteDataset?: () => void;
}
interface ListItem {
badge?: any;
buttons: any;
index: number;
isCreating?: boolean;
name: string;
}
const Working = () => <div className="d-flex align-items-center gap-2 py-1">
<p className="fw-medium opacity-75">
Voice is being created...
</p>
<div
className="spinner-border spinner-border-sm text-light"
role="status"
>
<span className="visually-hidden">Loading...</span>
</div>
</div>;
const ItemRow = ({ badge: Badge, buttons, index, isCreating, name, ...rest }: ListItem) => {
return <div className="d-flex flex-column flex-lg-row gap-3 list-items p-3 align-items-lg-center">
<div className="d-inline-flex flex-wrap align-items-center flex-grow-1 gap-2">
<h5 className="fw-semibold mb-0">
{ Badge ? <Badge /> : null }
{ name }
</h5>
</div>
<div className="d-flex">
{ isCreating ? <Working /> :
<div className="d-flex gap-2">
{ buttons && buttons.length ? buttons.map((action: any, key: number) => {
return <Button { ...{
...action,
key,
name: `item-row:${index},button:${key}`,
} }/>;
}) : null }
</div> }
</div>
</div>;
};
export default function NewList({ data, handleDeleteDataset, type, handleDeleteVoice, }: ListItemsProps) {
const DataPlaceholder = () => <div className="d-flex flex-column list-items p-5 align-items-center">
<h5 className="fw-semibold mb-3">
You haven't created any voices.
</h5>
<Button
icon={faPlus} // 1
label="Create New Voice" // 2
small={true}
to="/voice-designer/create" // 3
/>
</div>;
return data.length ? <div className="d-flex flex-column gap-3">
{ data.map((item, key) => <ItemRow {...{ key, ...item, index: key }}/>) }
</div> : <DataPlaceholder />;
};
@@ -8,11 +8,11 @@ import React, { useState } from "react";
import { FileUploader } from "react-drag-drop-files";
import { InputVcAudioPlayer } from "v2/view/_common/InputVcAudioPlayer";
import { v4 as uuidv4 } from "uuid";
import {
UploadAudio,
UploadAudioIsOk,
UploadAudioRequest,
} from "@storyteller/components/src/api/upload/UploadAudio";
// import {
// UploadAudio,
// UploadAudioIsOk,
// UploadAudioRequest,
// } from "@storyteller/components/src/api/upload/UploadAudio";
import useUploadedFiles from "hooks/useUploadedFiles";
const FILE_TYPES = ["MP3", "WAV", "FLAC", "OGG"];
@@ -34,6 +34,7 @@ function VoiceDesignerUploadComponent(props: Props) {
const setAudioLinks = useUploadedFiles((state: any) => state.setAudioLinks);
// Auto generated
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [idempotencyToken, setIdempotencyToken] = useState(uuidv4());
const handleChange = (file: any) => {
@@ -68,6 +69,7 @@ function VoiceDesignerUploadComponent(props: Props) {
? `${Math.floor(file.size / 1024)} KB`
: null;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const uploadBtnClass = isUploadDisabled
? "btn btn-uploaded w-100 disabled"
: "btn btn-primary w-100";
@@ -154,7 +156,7 @@ function VoiceDesignerUploadComponent(props: Props) {
</div>
);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const LoadingIcon: React.FC = () => {
return (
<>
@@ -3,6 +3,7 @@ import VoiceDesignerUploadComponent from "../VoiceDesignerUploadComponent";
import { v4 as uuidv4 } from "uuid";
function UploadSamples() {
/* eslint-disable @typescript-eslint/no-unused-vars */
const [convertLoading, setConvertLoading] = useState(false);
const [canConvert, setCanConvert] = useState(false);
const [mediaUploadToken, setMediaUploadToken] = useState<string | undefined>(
@@ -11,6 +12,8 @@ function UploadSamples() {
const [convertIdempotencyToken, setConvertIdempotencyToken] = useState(
uuidv4()
);
/* eslint-disable @typescript-eslint/no-unused-vars */
const [formIsCleared, setFormIsCleared] = useState(false);
const changeConvertIdempotencyToken = () => {
@@ -4,8 +4,8 @@ import {
// faMicrophoneLines,
// faTags,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button } from "components/common";
// import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// import { Button } from "components/common";
import Input from "components/common/Input";
import useVoiceDetailsStore from "hooks/useVoiceDetailsStore/";
import React from "react";
@@ -27,7 +27,7 @@ export const SearchFieldClass = {
};
function VoiceDetails() {
const { name, visibility, setName, setVisibility } = useVoiceDetailsStore();
const { visibility, setVisibility } = useVoiceDetailsStore();
return (
<div className="d-flex flex-column gap-4">
@@ -0,0 +1,31 @@
import { useEffect, useState } from 'react';
// import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { ListDatasetsByUser, Dataset } from "@storyteller/components/src/api/voice_designer/voice_datasets/ListDatasetsByUser";
import { DeleteDataset } from "@storyteller/components/src/api/voice_designer/voice_datasets/DeleteDataset";
import { useSession } from "hooks";
export default function useVoiceRequests() {
const [datasets, datasetsSet] = useState<Dataset[]>([]);
const { user } = useSession();
const deleteDataSet = (voiceToken: string) => DeleteDataset(voiceToken,{
set_delete: true,
as_mod: false
}).then(res => {
console.log("🏧",res);
});
const datasetByToken = (datasetToken?: string) => datasets.filter(({ dataset_token },i) => datasetToken === dataset_token)[0];
useEffect(() => {
if (!datasets.length && user && user.username) {
ListDatasetsByUser(user.username,{}).then(res => {
if (res.datasets) datasetsSet(res.datasets);
});
}
},[user, datasets]);
return { datasetByToken, datasets, deleteDataSet };
};
+25
View File
@@ -4574,6 +4574,7 @@ __metadata:
craco: ^0.0.3
cross-env: ^7.0.3
lerna: ^4.0.0
universal-cookie: ^6.1.1
yarn: ^1.22.19
languageName: unknown
linkType: soft
@@ -5103,6 +5104,13 @@ __metadata:
languageName: node
linkType: hard
"@types/cookie@npm:^0.5.1":
version: 0.5.3
resolution: "@types/cookie@npm:0.5.3"
checksum: b785618f6b2fdceb6a20a17e1dfe99651b1e5c2c079f486de76dfb21b508f09d91913755e4a6dbdfe628882ea32466bb2e9318b114ce34efa5e624356494fcab
languageName: node
linkType: hard
"@types/debounce@npm:*":
version: 1.2.1
resolution: "@types/debounce@npm:1.2.1"
@@ -8400,6 +8408,13 @@ __metadata:
languageName: node
linkType: hard
"cookie@npm:^0.5.0":
version: 0.5.0
resolution: "cookie@npm:0.5.0"
checksum: 1f4bd2ca5765f8c9689a7e8954183f5332139eb72b6ff783d8947032ec1fdf43109852c178e21a953a30c0dd42257828185be01b49d1eb1a67fd054ca588a180
languageName: node
linkType: hard
"copy-concurrently@npm:^1.0.0":
version: 1.0.5
resolution: "copy-concurrently@npm:1.0.5"
@@ -20366,6 +20381,16 @@ __metadata:
languageName: node
linkType: hard
"universal-cookie@npm:^6.1.1":
version: 6.1.1
resolution: "universal-cookie@npm:6.1.1"
dependencies:
"@types/cookie": ^0.5.1
cookie: ^0.5.0
checksum: 5c4d83f5879c5f133a51dadd080bf7a7a5c3a4b38695bb52ae560564884b84156d3c872abb1ade54a0ee00baf3870a59abc9907a4fa5bcf7a8d2d63af581c8e1
languageName: node
linkType: hard
"universal-user-agent@npm:^6.0.0":
version: 6.0.0
resolution: "universal-user-agent@npm:6.0.0"