Merge branch 'feature/mvp' into feature/comp/playbar-w-trim

This commit is contained in:
wil committed 2024-03-14 14:09:00 -04:00
commit dcafbfb537
18 files changed
+445 -375

No files matched your search

+4 -2
View File
@@ -46,7 +46,8 @@ class ApiConfig {
break;
case "storyteller.ai":
case "staging.storyteller.ai":
apiHost = "api.storyteller.ai";
//apiHost = "api.storyteller.ai";
apiHost = "api.fakeyou.com";
break;
case "storyteller.stream":
// Storyteller.stream is deprecated and will be decommissioned in the future.
@@ -61,7 +62,8 @@ class ApiConfig {
case "devproxy.storyteller.ai":
case "devproxy.storyteller.ai:7000":
case "devproxy.storyteller.ai:7001":
apiHost = "api.storyteller.ai";
//apiHost = "api.storyteller.ai";
apiHost = "api.fakeyou.com";
useSsl = true;
break;
case "dev.fakeyou.com":
+4 -2
View File
@@ -15,13 +15,15 @@ export default function GetApiHost() {
return hostConfig("api.fakeyou.com");
case "storyteller.ai":
case "staging.storyteller.ai":
return hostConfig("api.storyteller.ai");
//return hostConfig("api.storyteller.ai");
return hostConfig("api.fakeyou.com");
case "storyteller.stream": // Storyteller.stream is deprecated and will be decommissioned in the future.
return hostConfig("api.storyteller.stream");
case "devproxy.fakeyou.com":
return hostConfig("api.fakeyou.com");
case "devproxy.storyteller.ai":
return hostConfig("api.storyteller.ai");
//return hostConfig("api.storyteller.ai");
return hostConfig("api.fakeyou.com");
case "dev.fakeyou.com":
return hostConfig("api.dev.fakeyou.com:12345",false); // false disables SSL
default:
@@ -0,0 +1,27 @@
export enum MediaFileClass {
/// Unknown (default value)
/// This will be present until we migrate all old files.
Unknown = "unknown",
/// Audio files: wav, mp3, etc.
Audio = "audio",
/// Image files: png, jpeg, etc.
Image = "image",
/// Video files: mp4, etc.
Video = "video",
/// Engine "animations"
Animation = "animation",
/// Engine "characters"
Character = "character",
/// Engine "prop" items
Prop = "prop",
/// Engine scenes (internal and external scenes)
Scene = "scene",
}
@@ -1,12 +1,35 @@
import { useDocumentTitle } from "@storyteller/components/src/hooks/UseDocumentTitle"
import { DomainConfig, Website, getCurrentDomainConfig } from "utils/domainConfig";
const DEFAULT_FOR_EMPTY_TITLE = "FakeYou. Deep Fake Text to Speech.";
const FAKEYOU_DEFAULT_TITLE = "FakeYou. Deep Fake Text to Speech.";
const FAKEYOU_SUFFIX = "FakeYou";
const STORYTELLER_DEFAULT_TITLE = "Storyteller.ai - AI Film and Movie Tools";
const STORYTELLER_SUFFIX = "Storyteller.ai";
export function usePrefixedDocumentTitle(title?: string) {
const domainConfig : DomainConfig = getCurrentDomainConfig();
const fixed = title === undefined ? "" : title.trim();
let defaultTitle;
let defaultSuffix;
switch (domainConfig.website) {
case Website.FakeYou:
defaultTitle = FAKEYOU_DEFAULT_TITLE;
defaultSuffix = FAKEYOU_SUFFIX;
break;
case Website.StorytellerAi:
default:
defaultTitle = STORYTELLER_DEFAULT_TITLE;
defaultSuffix = STORYTELLER_SUFFIX;
break;
}
// 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 : `${fixed} | FakeYou`;
const outputTitle = fixed.length === 0 ? defaultTitle : `${fixed} | ${defaultSuffix}`;
useDocumentTitle(outputTitle);
}
@@ -4,11 +4,9 @@ import { a, useTransition } from "@react-spring/web";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
import Iframe from "react-iframe";
import { Label, Spinner } from "components/common";
import { AcceptTypes, EntityInputMode, EntityModeProp } from "components/entities/EntityTypes";
import { useFile, useMedia, useModal, useSession } from "hooks";
import { AcceptTypes, EntityInputMode, EntityModeProp, UploaderResponse } from "components/entities/EntityTypes";
import { useMedia, useMediaUploader, useModal, useSession } from "hooks";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import { UploadMedia, UploadMediaResponse } from "@storyteller/components/src/api/media_files/UploadMedia";
import { v4 as uuidv4 } from "uuid";
import EntityInputEmpty from "./EntityInputEmpty";
import "./EntityInput.scss";
@@ -77,22 +75,11 @@ export default function EntityInput({ accept: inAccept, aspectRatio = "square",
onChange({ target: { name, value: token } });
};
const { clear, inputProps } = useFile({
onChange: (inputFile: any) => {
if (inputFile) UploadMedia({
uuid_idempotency_token: uuidv4(),
file: inputFile,
source: "file",
})
.then((res: UploadMediaResponse) => {
if ("media_file_token" in res) {
clear();
selectToken(res.media_file_token);
}
});
else console.log("🥺 no file");
}
const { inputProps } = useMediaUploader({
autoUpload: true,
onSuccess: (res: UploaderResponse) => selectToken(res.media_file_token)
});
const onSelect = (data: MediaFile) => {
mediaSet(data);
selectToken(data.token);
@@ -1,5 +1,5 @@
import React from "react";
import { AcceptTypes, EntityInputMode, MediaFilters } from "components/entities/EntityTypes";
import { AcceptTypes, EntityInputMode, isSelectedType, MediaFilters } from "components/entities/EntityTypes";
import { SlideProps } from "./EntityInput";
import { MediaBrowser } from "components/modals";
import { FileWrapper } from "components/common";
@@ -23,26 +23,23 @@ interface EmptySlideProps extends SlideProps {
user: any
};
const mediaCheck = (value: string) => (value in MediaFilters);
export default function EntityInputEmpty({ accept, open, inputMode, inputProps, user, ...rest }: EmptySlideProps) {
const accepted = accept ? accept : [];
const isMedia = !!accepted.find(mediaCheck);
const isMedia = isSelectedType(MediaFilters.all,accepted[0]);
const browserClick = () => open({
component: MediaBrowser,
props: { accept, inputMode, username: user?.username || "", ...rest }
});
const mediaIcons = () => {
switch (accepted[0]) {
case "audio": return faWaveform;
case "image": return faImage;
case "bvh": return faPersonWalking;
default: return faFile;
}
if (isSelectedType(MediaFilters.audio,accepted[0])) return faWaveform;
if (isSelectedType(MediaFilters.engine_asset,accepted[0])) return faPersonWalking;
if (isSelectedType(MediaFilters.image,accepted[0])) return faImage;
if (isSelectedType(MediaFilters.video,accepted[0])) return faFile;
return faFile;
};
const supported = `${ accepted.length ? accepted.join(", ") : accepted[0] } files supported`;
return <>
@@ -1,3 +1,7 @@
import { enumToKeyArr } from "resources";
import { UploadMediaResponse } from "@storyteller/components/src/api/media_files/UploadMedia";
import { UploadEngineAssetResponse }from "@storyteller/components/src/api/media_files/UploadEngineAsset";
export enum EntityInputMode {
bookmarks,
media,
@@ -10,10 +14,7 @@ export enum MediaFilters {
audio,
image,
video,
bvh,
glb,
gltf,
fbx
engine_asset
}
export enum WeightsFilters {
@@ -36,19 +37,47 @@ export enum WeightsCategories {
voiceConversion
}
export type EntityModeProp = keyof typeof EntityInputMode;
export type MediaFilterProp = keyof typeof MediaFilters;
export type WeightFilterProp = keyof typeof WeightsFilters;
export type WeightCategoriesProp = keyof typeof WeightsCategories;
export type AcceptTypes = MediaFilterProp | WeightFilterProp;
export type JobSelection = WeightCategoriesProp | WeightFilterProp;
export enum EntityType {
unknown,
media,
weights
}
export enum EngineTypes {
bvh,
fbx,
glb,
gltf,
obj,
ron
}
export enum AudioTypes {
mp3,
wav
}
export enum ImageTypes {
jpg,
png
}
export enum VideoTypes {
mp4,
}
export type EntityModeProp = keyof typeof EntityInputMode;
export type MediaFilterProp = keyof typeof MediaFilters;
export type WeightFilterProp = keyof typeof WeightsFilters;
export type EngineFilterProp = keyof typeof EngineTypes;
export type AudioFilterProp = keyof typeof AudioTypes;
export type ImageFilterProp = keyof typeof ImageTypes;
export type VideoFilterProp = keyof typeof VideoTypes;
export type WeightCategoriesProp = keyof typeof WeightsCategories;
export type AcceptTypes = EngineFilterProp | AudioFilterProp | ImageFilterProp | VideoFilterProp | WeightFilterProp;
export type JobSelection = WeightCategoriesProp | WeightFilterProp;
export type UploaderResponse = UploadEngineAssetResponse | UploadMediaResponse;
export const ListEntityFilters = (mode?: number) => {
const bookmarkFilters = Object.keys({ ...MediaFilters, ...WeightsFilters }).filter(val => isNaN(Number(val))).reduce((obj,current) => ({ ...obj, [current]: current }),{});
@@ -62,4 +91,25 @@ export const EntityFilterOptions = (mode?: EntityInputMode, t = (v:string) => v)
if (typeof value === "string") return { value, label: t(value) }
return { label: "all", value: "all" };
});
};
};
export const isSelectedType = (mode: MediaFilters, fileExtension: string) => enumToKeyArr([
{ ...AudioTypes, ...ImageTypes, ...VideoTypes, ...EngineTypes },
AudioTypes,
ImageTypes,
VideoTypes,
EngineTypes
][mode]).includes(fileExtension);
export const getMediaCategory = (fileExtension: string) => {
isSelectedType(MediaFilters.image,fileExtension);
if (isSelectedType(MediaFilters.engine_asset, fileExtension)) return MediaFilters.engine_asset;
if (isSelectedType(MediaFilters.audio, fileExtension)) return MediaFilters.audio;
if (isSelectedType(MediaFilters.image, fileExtension)) return MediaFilters.image;
if (isSelectedType(MediaFilters.video, fileExtension)) return MediaFilters.video;
return MediaFilters.all; // will change to "unknown" eventually
}
@@ -30,6 +30,8 @@ import { useInferenceJobs, useLocalize } from "hooks";
import { Logout } from "@storyteller/components/src/api/session/Logout";
import { Button } from "components/common";
import { WebUrl } from "common/WebUrl";
import { DomainConfig, Website } from "utils/domainConfig";
import { useDomainConfig } from "context/DomainConfigContext";
interface SideNavProps {
sessionWrapper: SessionWrapper;
@@ -56,6 +58,7 @@ export default function SideNav({
const isOnLoginPage = window.location.pathname.includes("/login");
const isOnSignUpPage = window.location.pathname.includes("/signup");
const isOnStudioPage = window.location.pathname.includes("/studio");
const domain: DomainConfig = useDomainConfig();
let history = useHistory();
const handleNavLinkClick = () => {
@@ -107,7 +110,8 @@ export default function SideNav({
const shouldNotShowSidebar =
(!isLoggedIn && (isOnLandingPage || isOnLoginPage || isOnSignUpPage)) ||
isOnStudioPage;
isOnStudioPage ||
(domain.website === Website.StorytellerAi && isOnLandingPage);
const shouldShowSidebar = windowWidth >= 992 && !shouldNotShowSidebar;
const sidebarClassName = `sidebar ${
shouldShowSidebar ? "visible" : ""
@@ -333,7 +337,7 @@ export default function SideNav({
<li>
<NavLink
exact={true}
to="/"
to={domain.website === Website.FakeYou ? "/" : "/dashboard"}
activeClassName="active-link"
onClick={handleNavLinkClick}
>
@@ -341,7 +345,7 @@ export default function SideNav({
icon={faHome}
className="sidebar-heading-icon"
/>
Home
{domain.website === Website.FakeYou ? "Home" : "Dashboard"}
</NavLink>
</li>
<li>
+2
View File
@@ -14,6 +14,7 @@ import useLazyLists from './useLazyLists';
import useListContent from './useListContent';
import useLocalize from './useLocalize';
import useMedia from './useMedia';
import useMediaUploader from './useMediaUploader';
import useModal from './useModal';
import useNotifications from './useNotifications';
import useOnScreen from "./useOnScreen";
@@ -44,6 +45,7 @@ export {
useListContent,
useLocalize,
useMedia,
useMediaUploader,
useModal,
useNotifications,
useOnScreen,
@@ -19,7 +19,7 @@ export default function useFile({ debug, onChange = n, onClear = n }: Props) {
blobSet(inputFile ? URL.createObjectURL(inputFile) : "");
};
const inputChange = ({ target = {} }: { target: any }) => {
fileChange(target.value);
fileChange(target.files ? target.files[0] : target.value);
};
const clear = () => {
if (inputRef?.current?.value) inputRef.current.value = '';
@@ -0,0 +1 @@
export { default } from "./useMediaUploader";
@@ -0,0 +1,66 @@
import { useState } from "react";
import { useFile } from "hooks";
import { v4 as uuidv4 } from "uuid";
import { isSelectedType, MediaFilters, UploaderResponse } from "components/entities/EntityTypes";
import { UploadMedia } from "@storyteller/components/src/api/media_files/UploadMedia";
import { UploadEngineAsset }from "@storyteller/components/src/api/media_files/UploadEngineAsset";
import { MediaFileSubtype } from "@storyteller/components/src/api/enums/MediaFileSubtype";
import { GetFileTypeByExtension as extension } from "@storyteller/components/src/utils/GetFileTypeByExtension";
import { MediaFileClass } from "@storyteller/components/src/api/enums/MediaFileClass";
interface Props {
autoUpload?: boolean,
onSuccess?: (res: UploaderResponse) => any;
}
const n = () => {};
export default function useMediaUploader({ autoUpload, onSuccess = n }: Props) {
const [mediaClass,mediaClassSet] = useState<MediaFileClass>(MediaFileClass.Unknown);
const [engineSubtype,engineSubtypeSet] = useState<MediaFileSubtype | undefined>();
const createUpload = (inputFile: File, todo = n ) => {
const fileExtension = extension(inputFile.name || "");
const isEngineAsset = isSelectedType(MediaFilters.engine_asset, fileExtension);
const baseConfig = { uuid_idempotency_token: uuidv4(), file: inputFile };
const engineConfig = { ...baseConfig, media_file_subtype: engineSubtype, media_file_class: mediaClass };
const mediaConfig = { ...baseConfig, source: "file" };
const uploader = isEngineAsset ? UploadEngineAsset(engineConfig) : UploadMedia(mediaConfig);
if (inputFile) {
uploader
.then((res: UploaderResponse) => {
if ("media_file_token" in res) {
onSuccess(res);
todo();
}
});
}
};
const { file, clear, inputProps } = useFile({
...autoUpload ? { onChange: (inputFile: File) => createUpload(inputFile,clear) } : {}
});
const upload = () => createUpload(file,clear);
const engineSubtypeChange = ({ target }: { target: any }) => engineSubtypeSet(target.value);
const mediaClassChange = ({ target }: { target: any }) => mediaClassSet(target.value);
return {
clear,
engineSubtype,
engineSubtypeChange,
mediaClass,
mediaClassChange,
file,
inputProps,
isAudio: isSelectedType(MediaFilters.audio, extension(file?.name || "")),
isEngineAsset: isSelectedType(MediaFilters.engine_asset, extension(file?.name || "")),
isImage: isSelectedType(MediaFilters.image, extension(file?.name || "")),
isVideo: isSelectedType(MediaFilters.video, extension(file?.name || "")),
upload
}
};
@@ -127,6 +127,7 @@ import DevTTS from "./pages/dev_tts/DevTTS";
import { StudioIntroPage } from "./pages/storyteller_studio_intro/StudioIntroPage";
import StudioVSTPage from "./pages/storyteller_studio_intro/StudioVST/StudioVSTPage";
import { StudioIntroResultPage } from "./pages/storyteller_studio_intro/StudioIntroResultPage";
import DashboardPage from "./pages/dashboard/DashboardPage";
interface Props {
sessionWrapper: SessionWrapper;
@@ -727,6 +728,10 @@ class PageContainer extends React.Component<
/>
</Route>
<Route path="/dashboard">
<DashboardPage sessionWrapper={this.props.sessionWrapper} />
</Route>
<Route path="/about">
<AboutPage />
</Route>
@@ -812,7 +817,7 @@ class PageContainer extends React.Component<
<Route path="/dev-tts">
<DevTTS />
</Route>
{/* Route for initial voice creation */}
<Route exact path="/voice-designer/create">
<VoiceDesignerFormPage
@@ -0,0 +1,19 @@
import React from "react";
import Dashboard from "../landing/Dashboard";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
import { Container } from "components/common";
interface DashboardPageProps {
sessionWrapper: SessionWrapper;
}
export default function DashboardPage({ sessionWrapper }: DashboardPageProps) {
usePrefixedDocumentTitle("AI Tools");
return (
<Container type="panel">
<Dashboard sessionWrapper={sessionWrapper} />
</Container>
);
}
@@ -1,142 +1,68 @@
import React, { useState } from "react";
import Panel from "components/common/Panel";
import Container from "components/common/Container";
import PageHeader from "components/layout/PageHeader";
import { useSession } from "hooks";
import { Button } from "components/common";
import { useMediaUploader, useSession } from "hooks";
import { Button, Container, Panel } from "components/common";
import { UploaderResponse } from "components/entities/EntityTypes";
import { faUpload } from "@fortawesome/pro-solid-svg-icons";
import { FileType, GetFileTypeByExtension } from "@storyteller/components/src/utils/GetFileTypeByExtension";
import { UploadMedia, UploadMediaResponse, } from "@storyteller/components/src/api/media_files/UploadMedia";
import { UploadEngineAsset, UploadEngineAssetResponse, } from "@storyteller/components/src/api/media_files/UploadEngineAsset";
import { MediaFileSubtype } from "@storyteller/components/src/api/enums/MediaFileSubtype";
import { v4 as uuidv4 } from "uuid";
import { MediaFileClass } from "@storyteller/components/src/api/enums/MediaFileClass";
import { Link } from "react-router-dom";
enum UploadType {
EngineAsset = "engine_asset",
Image = "image",
Audio = "audio",
Video = "video",
Unknown = "unknown",
}
interface DevUploadProps {}
export default function DevUpload(props: DevUploadProps) {
const [tokens, tokensSet] = useState<string[]>([]);
const { engineSubtype, engineSubtypeChange, mediaClass, mediaClassChange, file, inputProps, isEngineAsset, isVideo, upload } = useMediaUploader({
onSuccess: (res: UploaderResponse) => tokensSet([res.media_file_token, ...tokens])
});
const { onChange } = inputProps;
const { studioAccessCheck } = useSession();
const [file, fileSet] = useState<File | null>(null);
const [maybeMediaFileSubtype, maybeMediaFileSubtypeSet] = useState<MediaFileSubtype | undefined>(undefined);
const [uploadType, uploadTypeSet] = useState<UploadType>(UploadType.Unknown);
const [tokens, tokensSet] = useState<string[]>([]);
const handleFileChange = (event: any) => {
const maybeFile = event.target.files[0];
const fileType = GetFileTypeByExtension(maybeFile?.name || "");
let uploadType = UploadType.Unknown;
switch (fileType) {
case FileType.Bvh:
case FileType.Fbx:
case FileType.Glb:
case FileType.Gltf:
case FileType.Obj:
case FileType.Ron:
uploadType = UploadType.EngineAsset;
break;
case FileType.Jpg:
case FileType.Png:
uploadType = UploadType.Image;
break;
case FileType.Mp3:
case FileType.Wav:
uploadType = UploadType.Audio;
break;
case FileType.Mp4:
uploadType = UploadType.Video;
break;
case FileType.Unknown:
default:
uploadType = UploadType.Unknown;
break;
}
fileSet(maybeFile);
uploadTypeSet(uploadType);
};
const handleSubtypeChange = (ev: React.FormEvent<HTMLSelectElement>) => {
const value = (ev.target as HTMLSelectElement).value;
const maybeSubtype = value as MediaFileSubtype;
maybeMediaFileSubtypeSet(maybeSubtype);
}
const handleUpload = () => {
if (!file) {
console.error("no file specified for upload");
return;
}
switch(uploadType) {
case UploadType.EngineAsset:
UploadEngineAsset({
uuid_idempotency_token: uuidv4(),
file,
media_file_subtype: maybeMediaFileSubtype,
})
.then((res: UploadEngineAssetResponse) => {
if ("media_file_token" in res) {
tokensSet([res.media_file_token, ...tokens]);
fileSet(null);
}
});
break;
default:
UploadMedia({
uuid_idempotency_token: uuidv4(),
file,
source: "file",
})
.then((res: UploadMediaResponse) => {
if ("media_file_token" in res) {
tokensSet([res.media_file_token, ...tokens]);
fileSet(null);
}
});
}
};
let title = "Upload Generic File";
let mediaFileSubtypeForm = <></>;
let engineSubForm = <></>;
switch (uploadType) {
case UploadType.Video:
title = "Upload Video";
break;
case UploadType.EngineAsset:
title = "Upload Engine Asset";
mediaFileSubtypeForm = (
<>
<div className="mb-3">
<label htmlFor="fileSubtypeSelect" className="form-label">Engine Media File Subtype</label>
<select
onChange={handleSubtypeChange}
className="form-select"
aria-label="Default select example"
id="fileSubtypeSelect"
value={maybeMediaFileSubtype}
>
<option value={MediaFileSubtype.SceneImport}>Scene Import (default)</option>
<option value={MediaFileSubtype.Mixamo}>Mixamo Animation</option>
<option value={MediaFileSubtype.StorytellerScene}>Storyteller Scene</option>
<option value={MediaFileSubtype.AnimationOnly}>Animation Only</option>
</select>
</div>
</>
);
break;
if (isVideo) { title = "Upload Video"; }
if (isEngineAsset) {
title = "Upload Engine Asset";
engineSubForm = (
<>
<div className="mb-3">
<label htmlFor="fileClassSelect" className="form-label">Media Class</label>
<select
onChange={mediaClassChange}
className="form-select"
aria-label="Default select example"
id="fileClassSelect"
value={mediaClass}
>
<option value={MediaFileClass.Unknown}>Unknown</option>
<option value={MediaFileClass.Audio}>Audio</option>
<option value={MediaFileClass.Image}>Image</option>
<option value={MediaFileClass.Video}>Video</option>
<option value={MediaFileClass.Animation}>Animation</option>
<option value={MediaFileClass.Character}>Character</option>
<option value={MediaFileClass.Prop}>Prop</option>
<option value={MediaFileClass.Scene}>Scene</option>
</select>
</div>
<div className="mb-3">
<label htmlFor="fileSubtypeSelect" className="form-label">Engine Media File Subtype</label>
<select
onChange={engineSubtypeChange}
className="form-select"
aria-label="Default select example"
id="fileSubtypeSelect"
value={engineSubtype}
>
<option value={MediaFileSubtype.SceneImport}>Scene Import (default)</option>
<option value={MediaFileSubtype.Mixamo}>Mixamo Animation</option>
<option value={MediaFileSubtype.StorytellerScene}>Storyteller Scene</option>
<option value={MediaFileSubtype.AnimationOnly}>Animation Only</option>
</select>
</div>
</>
);
}
return studioAccessCheck(
@@ -146,7 +72,7 @@ export default function DevUpload(props: DevUploadProps) {
subText="Upload files to the server for testing."
/>
{maybeMediaFileSubtype}
{engineSubtype}
<Panel padding={true}>
<div className="d-flex flex-column gap-5">
@@ -154,22 +80,22 @@ export default function DevUpload(props: DevUploadProps) {
<div className="mb-3">
<label htmlFor="formFile" className="form-label">Select File for Upload</label>
<input
className="form-control form-control-lg"
id="formFile"
type="file"
onChange={handleFileChange}
/>
<input {...{
onChange,
className: "form-control form-control-lg",
type: "file",
id: "formFile"
}}/>
</div>
{mediaFileSubtypeForm}
{engineSubForm}
<div className="d-flex gap-3 justify-content-end">
<Button
disabled={!file}
icon={faUpload}
label="Upload Media"
onClick={handleUpload}
onClick={upload}
/>
</div>
@@ -1,24 +1,26 @@
import React, { useState } from "react";
import {
faArrowRightArrowLeft,
faFile,
faTrashAlt,
// faFile,
// faTrashAlt,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button, Container, Panel } from "components/common";
import FileInput from "components/common/FileInput";
// import FileInput from "components/common/FileInput";
import PageHeader from "components/layout/PageHeader";
import { useFile, useInferenceJobs, useMedia } from "hooks";
import React, { useEffect, useState } from "react";
import { useHistory, useParams } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
import { UploadMedia } from "@storyteller/components/src/api/media_files/UploadMedia";
// import { useHistory, useParams } from "react-router-dom";
// import { v4 as uuidv4 } from "uuid";
// import { UploadMedia } from "@storyteller/components/src/api/media_files/UploadMedia";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import FbxToGltfJobList from "./components/FbxToGltfJobList";
import {
EnqueueFbxToGltf,
EnqueueFbxToGltfIsSuccess,
EnqueueFbxToGltfIsError,
// EnqueueFbxToGltf,
// EnqueueFbxToGltfIsSuccess,
// EnqueueFbxToGltfIsError,
} from "@storyteller/components/src/api/file_conversion/EnqueueFbxToGltf";
import { onChanger } from "resources";
import { EntityInput } from "components/entities";
interface FbxToGltfPageProps {
enqueueInferenceJob: (
@@ -30,117 +32,39 @@ interface FbxToGltfPageProps {
export default function FbxToGltfPage({
enqueueInferenceJob,
}: FbxToGltfPageProps) {
const history = useHistory();
const { mediaToken: mediaTokenParam } = useParams<{ mediaToken: string }>();
const [mediaToken, setMediaToken] = useState<string | null>(mediaTokenParam);
const { media: presetFile } = useMedia({
mediaToken: mediaToken ?? undefined,
});
const fileProps = useFile({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isWorking, setIsWorking] = useState(false);
const [mediaToken,mediaTokenSet] = useState();
const onChange = onChanger({ mediaTokenSet });
const { inferenceJobs } = useInferenceJobs(
FrontendInferenceJobType.ConvertFbxtoGltf
);
// const EnqueueConvert = async ({ upload_token }: any) => {
// if (!upload_token) return false;
console.log("inferenceJobs", inferenceJobs);
// try {
// let request = {
// uuid_idempotency_token: uuidv4(),
// file_source: undefined,
// media_file_token: upload_token,
// };
useEffect(() => {
// If there's a mediaToken, automatically prepare the file for conversion
if (mediaToken && presetFile) {
}
}, [mediaToken, presetFile]);
// const response = await EnqueueFbxToGltf(request);
const clearMediaToken = () => {
// Reset the media token state
setMediaToken(null);
history.push("/fbx-to-gltf");
};
// if (EnqueueFbxToGltfIsSuccess(response)) {
// console.log("Enqueue successful");
const makeFBXUploadRequest = () => ({
uuid_idempotency_token: uuidv4(),
file: fileProps.file,
source: "file",
type: "fbx/model",
});
const handleUploadFBX = async () => {
try {
const res = await UploadMedia(makeFBXUploadRequest());
if (res.success && res.media_file_token) {
return { upload_token: res.media_file_token };
}
console.error("Upload failed:", res);
return null;
} catch (error) {
console.error("Error in upload:", error);
return null;
}
};
const submit = async () => {
if (isSubmitting) return;
setIsSubmitting(true);
setIsWorking(true);
try {
let uploadToken = mediaToken;
// If no media token, upload the file first
if (!mediaToken && fileProps.file) {
const uploadResponse = await handleUploadFBX();
if (uploadResponse && uploadResponse.upload_token) {
uploadToken = uploadResponse.upload_token;
} else {
console.error("Failed to upload file. Response:", uploadResponse);
throw new Error("File upload failed");
}
}
// Enqueue the conversion job
if (uploadToken) {
await EnqueueConvert({ upload_token: uploadToken });
} else {
console.error("No upload token available for conversion");
}
} catch (error) {
console.error("Error in submit process: ", error);
} finally {
setIsWorking(false);
setIsSubmitting(false);
}
};
const EnqueueConvert = async ({ upload_token }: any) => {
if (!upload_token) return false;
try {
let request = {
uuid_idempotency_token: uuidv4(),
file_source: undefined,
media_file_token: upload_token,
};
const response = await EnqueueFbxToGltf(request);
if (EnqueueFbxToGltfIsSuccess(response)) {
console.log("Enqueue successful");
if (response && response.inference_job_token) {
enqueueInferenceJob(
response.inference_job_token,
FrontendInferenceJobType.ConvertFbxtoGltf
);
}
return true;
} else if (EnqueueFbxToGltfIsError(response)) {
throw new Error("Enqueue failed");
}
} catch (error) {
console.error("Error in enqueueing conversion: ", error);
return false;
}
};
// if (response && response.inference_job_token) {
// enqueueInferenceJob(
// response.inference_job_token,
// FrontendInferenceJobType.ConvertFbxtoGltf
// );
// }
// return true;
// } else if (EnqueueFbxToGltfIsError(response)) {
// throw new Error("Enqueue failed");
// }
// } catch (error) {
// console.error("Error in enqueueing conversion: ", error);
// return false;
// }
// };
return (
<Container type="panel">
@@ -156,47 +80,59 @@ export default function FbxToGltfPage({
<Panel padding={true}>
<div className="d-flex flex-column gap-3">
{mediaToken && presetFile ? (
<div>
<label className="sub-title">FBX file from media</label>
<Panel className="panel-inner p-3 rounded">
<div className="d-flex gap-3 align-items-center flex-wrap">
<div className="d-flex gap-3 flex-grow-1 align-items-center">
<FontAwesomeIcon icon={faFile} className="display-6" />
<div>
<h6 className="mb-1">{presetFile.token}</h6>
<p className="opacity-75">
Created by {presetFile.maybe_creator_user?.display_name}
</p>
</div>
</div>
<Button
icon={faTrashAlt}
square={true}
onClick={clearMediaToken}
variant="danger"
small={true}
tooltip="Remove file"
/>
</div>
</Panel>
</div>
) : (
<FileInput
{...fileProps}
label="Select FBX File"
fileTypes={["FBX"]}
mediaToken={mediaToken}
/>
)}
<EntityInput {...{
accept: ["fbx"],
aspectRatio: "landscape",
label: "Choose FBX file",
name: "mediaToken",
onChange,
// owner: "echelon",
type: "media"
}}/>
{
// mediaToken && presetFile ? (
// <div>
// <label className="sub-title">FBX file from media</label>
// <Panel className="panel-inner p-3 rounded">
// <div className="d-flex gap-3 align-items-center flex-wrap">
// <div className="d-flex gap-3 flex-grow-1 align-items-center">
// <FontAwesomeIcon icon={faFile} className="display-6" />
// <div>
// <h6 className="mb-1">{presetFile.token}</h6>
// <p className="opacity-75">
// Created by {presetFile.maybe_creator_user?.display_name}
// </p>
// </div>
// </div>
// <Button
// icon={faTrashAlt}
// square={true}
// onClick={clearMediaToken}
// variant="danger"
// small={true}
// tooltip="Remove file"
// />
// </div>
// </Panel>
// </div>
// ) : (
// <FileInput
// {...fileProps}
// label="Select FBX File"
// fileTypes={["FBX"]}
// mediaToken={mediaToken}
// />
// )
}
<div className="d-flex justify-content-end">
<Button
icon={faArrowRightArrowLeft}
label="Convert to glTF"
onClick={submit}
disabled={!mediaToken && !fileProps.file}
isLoading={isWorking}
// onClick={submit}
disabled={!mediaToken}
isLoading={false} // REPLACE
/>
</div>
</div>
@@ -35,20 +35,34 @@ interface Props {
}
function LandingPage(props: Props) {
usePrefixedDocumentTitle("FakeYou Celebrity Voice Generator");
PosthogClient.recordPageview();
const domain: DomainConfig = useDomainConfig();
const webpageTitle =
domain.website === Website.FakeYou
? "FakeYou Celebrity Voice Generator"
: "";
usePrefixedDocumentTitle(webpageTitle);
const isLoggedIn = props.sessionWrapper.isLoggedIn();
const canAccessStudio = props.sessionWrapper.canAccessStudio();
// DO NOT LEAK THIS YET!!
let protectedStudioOnboarding = <></>;
if (props.sessionWrapper.canAccessStudio()) {
protectedStudioOnboarding = (
<>
<OnboardingSelection />
</>
);
}
return (
<>
{domain.website === Website.StorytellerAi && !isLoggedIn && (
<LandingVideoReel />
{domain.website === Website.StorytellerAi && (
<LandingVideoReel sessionWrapper={props.sessionWrapper} />
)}
<Container type="panel">
{domain.website === Website.FakeYou ? (
@@ -79,27 +93,22 @@ function LandingPage(props: Props) {
) : (
<>
{/* STORYTELLER,AI */}
{!isLoggedIn ? (
<>
{!canAccessStudio && <OnboardingSelection />}
<VstSectionV2 />
<TtsDemoSection
sessionWrapper={props.sessionWrapper}
sessionSubscriptionsWrapper={
props.sessionSubscriptionsWrapper
}
inferenceJobs={props.inferenceJobs}
ttsInferenceJobs={props.ttsInferenceJobs}
enqueueInferenceJob={props.enqueueInferenceJob}
inferenceJobsByCategory={props.inferenceJobsByCategory}
enqueueTtsJob={props.enqueueTtsJob}
/>
<Dashboard sessionWrapper={props.sessionWrapper} />
<FakeYouLandingBody />
</>
) : (
<Dashboard sessionWrapper={props.sessionWrapper} />
)}
{protectedStudioOnboarding}
<VstSectionV2 />
<TtsDemoSection
sessionWrapper={props.sessionWrapper}
sessionSubscriptionsWrapper={props.sessionSubscriptionsWrapper}
inferenceJobs={props.inferenceJobs}
ttsInferenceJobs={props.ttsInferenceJobs}
enqueueInferenceJob={props.enqueueInferenceJob}
inferenceJobsByCategory={props.inferenceJobsByCategory}
enqueueTtsJob={props.enqueueTtsJob}
/>
<Dashboard sessionWrapper={props.sessionWrapper} />
<FakeYouLandingBody />
</>
)}
</Container>
@@ -1,12 +1,26 @@
import { faArrowRight } from "@fortawesome/pro-solid-svg-icons";
import { Button } from "components/common";
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import "./LandingVideoReel.scss";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useLocation } from "react-router-dom";
interface LandingVideoReelProps {}
interface LandingVideoReelProps {
sessionWrapper: SessionWrapper;
}
export default function LandingVideoReel(props: LandingVideoReelProps) {
const isOnLandingPage = window.location.pathname === "/";
export default function LandingVideoReel({
sessionWrapper,
}: LandingVideoReelProps) {
const location = useLocation();
const [isOnLandingPage, setIsOnLandingPage] = useState(
window.location.pathname === "/"
);
const isLoggedIn = sessionWrapper.isLoggedIn();
useEffect(() => {
setIsOnLandingPage(location.pathname === "/");
}, [location]);
useEffect(() => {
const contentWrapper = document.getElementById("page-content-wrapper");
@@ -41,7 +55,7 @@ export default function LandingVideoReel(props: LandingVideoReelProps) {
searchBar?.classList.remove("landing-search");
contentWrapper?.classList.remove("no-page-padding-top");
};
}, [isOnLandingPage]);
}, [isOnLandingPage, isLoggedIn]);
return (
<div className="storyteller-landing-reel">
@@ -62,8 +76,8 @@ export default function LandingVideoReel(props: LandingVideoReelProps) {
<Button
icon={faArrowRight}
iconFlip={true}
label="Sign Up Now"
to="/signup"
label={isLoggedIn ? "Explore AI Tools" : "Sign Up Now"}
to={isLoggedIn ? "/dashboard" : "/signup"}
className="mt-4"
/>
</div>