Merge pull request #463 from storytold/feature/page_st_filter

Feature/page st filter
This commit is contained in:
wil authored and GitHub committed 2024-01-30 18:17:57 -05:00
commit b656d2d3a5
15 files changed
+463 -11

No files matched your search

@@ -0,0 +1,12 @@
import React from 'react';
import { useLocalize } from "hooks";
export default function Error({message}:{message?:string}){
const {t} = useLocalize("App")
return (
<div className="row">
<div className="col-12">
<h1>{message ? message : t("error.unknownError")}</h1>
</div>
</div>
);
}
@@ -21,12 +21,14 @@ interface SelectModalProps {
label?: string;
tabs: TabConfig[];
modalTitle?: string;
onSelect?: (token: string)=>void;
}
const SelectModal = memo(
({ label, tabs, modalTitle = "Select" }: SelectModalProps) => {
({ label, tabs, modalTitle = "Select", onSelect }: SelectModalProps) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const { token, setToken, weightTitle, setWeightTitle } = useToken();
const [selectedValue, setSelectedValue] = useState("");
const [activeTab, setActiveTab] = useState(tabs[0].tabKey);
const [mediaType, setMediaType] = useState(
tabs[0].mediaTypeFilter || "all"
@@ -42,6 +44,16 @@ const SelectModal = memo(
setWeightType(currentTab?.mediaTypeFilter || "all");
}, [activeTab, tabs]);
useEffect(()=>{
if(token && token !== selectedValue && onSelect){
setSelectedValue(token);
onSelect(token);
}else if(weightTitle && weightTitle !== selectedValue && onSelect){
setSelectedValue(weightTitle);
onSelect(weightTitle);
}
}, [token, weightTitle, selectedValue, onSelect]);
const openModal = () => {
setIsModalOpen(true);
};
@@ -5,6 +5,7 @@ import Button from "./Button";
import Checkbox from "./Checkbox";
import Container from "./Container";
import CoverImageInput from "./CoverImageInput";
import ErrorMessage from "./ErrorMessage"
import FileActions from "./FileActions";
import FileDetails from "./FileDetails";
import FileWrapper from "./FileWrapper";
@@ -22,11 +23,13 @@ import Skeleton from "./Skeleton";
import Slider from "./Slider";
import Spinner from "./Spinner";
import SplitPanel from "./SplitPanel";
import Tabs from "./Tabs";
import TempAudioPlayer from "./TempAudioPlayer";
import TempInput from "./TempInput";
import TempSelect from "./TempSelect";
import TempTextArea from "./TempTextArea";
import TintSpinner from "./TintSpinner";
import VideoInput from "./VideoInput";
export {
AudioBlobPreview,
@@ -36,6 +39,7 @@ export {
Checkbox,
Container,
CoverImageInput,
ErrorMessage,
FileActions,
FileDetails,
FileWrapper,
@@ -53,9 +57,11 @@ export {
Slider,
Spinner,
SplitPanel,
Tabs,
TempAudioPlayer,
TempInput,
TempSelect,
TempTextArea,
TintSpinner
TintSpinner,
VideoInput
};
@@ -108,6 +108,7 @@ import UploadSdWeightPage from "./pages/upload/UploadSdWeightPage";
import { FooterNav } from "./nav/FooterNav";
import FbxToGltfPage from "./pages/fbx_to_gltf/FbxToGltfPage";
import UploadLoraWeightPage from "./pages/upload/UploadLoraWeightPage";
import StorytellerFilterPage from "./pages/storyteller_filter/StorytellerFilter";
interface Props {
sessionWrapper: SessionWrapper;
@@ -818,6 +819,19 @@ class PageContainer extends React.Component<
/>
</Route>
<Route path="/storyteller-filter/:mediaToken?">
<StorytellerFilterPage
{...{
enqueueInferenceJob: this.props.enqueueInferenceJob,
sessionSubscriptionsWrapper:
this.props.sessionSubscriptionsWrapper,
inferenceJobs: this.props.inferenceJobs,
inferenceJobsByCategory:
this.props.inferenceJobsByCategory,
}}
/>
</Route>
<Route path="/character/donald-trump">
<TrumpTtsPage
sessionWrapper={this.props.sessionWrapper}
@@ -0,0 +1,56 @@
import React, {useReducer } from 'react';
import { useLocalize } from "hooks";
import { FrontendInferenceJobType, InferenceJob } from '@storyteller/components/src/jobs/InferenceJob';
import { Container, ErrorMessage } from "components/common";
import PageHeader from "components/layout/PageHeader";
import PageVideoProvision from './components/pageVideoProvision';
import PageFilterControls from './components/pageFilterControls';
import { states, reducer } from "./storytellerFilterReducer";
export default function StorytellerFilter(props:{
enqueueInferenceJob: (
jobToken: string,
frontendInferenceJobType: FrontendInferenceJobType
) => void;
inferenceJobs: Array<InferenceJob>;
inferenceJobsByCategory: Map<FrontendInferenceJobType, Array<InferenceJob>>;
}){
const debug=true;
const { t } = useLocalize("StorytellerFilter");
const { NO_FILE, FILE_STAGED, FILE_UPLOADING, FILE_UPLOADED, FILE_SELECTED, FILE_LOADING, FILE_LOADED } = states;
const [pageState, dispatchPageState] = useReducer(reducer, {
status: NO_FILE,
});
const pagePicker = () => {
switch (pageState.status){
case NO_FILE:
case FILE_STAGED:
case FILE_UPLOADING:
case FILE_SELECTED:
return <PageVideoProvision {...{debug, t, pageState, dispatchPageState}}/>
case FILE_UPLOADED:
case FILE_LOADING:
case FILE_LOADED:
return <PageFilterControls {...{debug, t, pageState, dispatchPageState}}/>
default:
return <ErrorMessage/>
}
}
const currPage = pagePicker();
return(
<Container type="panel" className="mb-5">
<PageHeader
title={t("headings.title")}
subText={t("headings.subtitle")}
// imageUrl="/images/header/video-mocap.png"
/>
{currPage}
</Container>
);
}
@@ -0,0 +1,54 @@
import React from "react";
import { useMedia } from "hooks";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import { states, Action, State } from "../storytellerFilterReducer";
import { BasicVideo, ErrorMessage, Panel, Spinner } from "components/common";
export default function PageFilterControls({
debug=false, t, pageState, dispatchPageState
}: {
debug?: boolean;
t: Function;
pageState: State;
dispatchPageState: (action: Action) => void;
}) {
useMedia({
mediaToken: pageState.mediaFileToken,
onSuccess: (res: any) => {
// ratings.gather({ res, key: "token" });
console.log(res)
dispatchPageState({
type: 'loadFileSuccess',
payload: {mediaFile: res}
})
},
});
if (pageState.mediaFile){
const mediaLink = new BucketConfig().getGcsUrl(pageState.mediaFile.public_bucket_path);
if (mediaLink)
return(
<Panel>
<div className="row g-3 p-3">
{/* <h1>{t("message.fileUploaded")}</h1> */}
{debug && <p>{`File Token: ${pageState.mediaFileToken}`}</p> }
<div className="col-6">
<BasicVideo src={mediaLink} />
</div>
<div className="col-6">
<BasicVideo src={mediaLink} />
</div>
</div>
</Panel>
);
}else if (pageState.status <= states.FILE_LOADING){
return (
<Panel>
{debug && <p>{`File Token: ${pageState.mediaFileToken}`}</p> }
<p>Loading Files</p>
<Spinner />
</Panel>
);
}
return <ErrorMessage />;
}
@@ -0,0 +1,60 @@
import React from "react";
import { Redirect, useLocation } from "react-router-dom";
import { BasicVideo, Panel, Tabs } from "components/common";
import { Action, State } from "../storytellerFilterReducer";
import TabContentUpload from "./tabContentUpload";
import TabContentLibrary from "./tabContentLibrary";
export default function PageVideoProvision({
debug=false, t, pageState, dispatchPageState
}: {
debug?: boolean;
t: Function;
pageState: State;
dispatchPageState: (action: Action) => void;
}) {
const tabs = [
{
label: t("tabTitle.upload"),
content: <TabContentUpload {...{
debug, t, pageState, dispatchPageState
}} />,
to: "/storyteller-filter/upload",
padding: true,
},
{
label: t("tabTitle.library"),
content: <TabContentLibrary {...{
debug, t, pageState, dispatchPageState
}} />,
to: "/storyteller-filter/select-media",
padding: true,
},
];
//pick default tab using pathname
const { pathname } = useLocation();
if (pathname === "/storyteller-filter" || pathname === "/storyteller-filter/") {
return <Redirect to={"/storyteller-filter/upload"} />;
}
return(
<Panel>
<div className="row g-0">
<div className="col-12 col-md-6">
<Tabs tabs={tabs} />
</div>
<div className="col-12 col-md-6">
<Panel padding={true} clear={true}>
<BasicVideo
title={t("video.sample")}
src="/videos/face-animator-instruction-en.mp4"
/>
</Panel>
</div>
</div>
</Panel>
);
}
@@ -0,0 +1,46 @@
import SelectModal from "components/common/SelectModal/SelectModal";
import React from "react";
import { states, Action, State } from "../storytellerFilterReducer";
import { Button } from "components/common";
export default function TabContentLibrary({
t, pageState, dispatchPageState
}: {
t: Function;
pageState: State;
dispatchPageState: (action: Action) => void;
}){
const handleProceed = ()=>{
dispatchPageState({type: "loadFile"})
}
const handleOnSelect = (token:string)=>{
dispatchPageState({
type: "selectedFile",
payload: {mediaFileToken: token}
});
};
return (
<div>
<SelectModal
modalTitle="Select a Video"
label="Select a Video"
onSelect={handleOnSelect}
tabs={[
{
label: "All Videos",
tabKey: "allVideos",
mediaTypeFilter: "video",
searcher: false,
type: "media",
},
]}
/>
{pageState.status === states.FILE_SELECTED &&
<Button
label={t("button.proceed")}
onClick={handleProceed}
/>
}
</div>
);
}
@@ -0,0 +1,90 @@
import React from "react";
import { v4 as uuidv4 } from "uuid";
import { UploadMedia } from "@storyteller/components/src/api/media_files/UploadMedia";
import { useFile } from "hooks";
import { ErrorMessage, VideoInput, Spinner } from "components/common";
import { states, Action, State } from "../storytellerFilterReducer";
export default function TabContentUpload({
debug=false, t, pageState, dispatchPageState
}: {
debug?: boolean;
t: Function;
pageState: State;
dispatchPageState: (action: Action) => void;
}) {
const videoProps = useFile({});
const {NO_FILE, FILE_STAGED, FILE_UPLOADING} = states;
const makeVideoUploadRequest = () => ({
uuid_idempotency_token: uuidv4(),
file: videoProps.file,
source: "file",
type: "video",
});
const handleUploadVideo = () => {
dispatchPageState({type: 'uploadFile'});
UploadMedia(makeVideoUploadRequest()).then(res => {
if (res.success && res.media_file_token) {
dispatchPageState({
type: 'uploadFileSuccess',
payload:{
mediaFileToken :res.media_file_token
}
})
}
});
};
// contains upload inout state and controls, see docs
if (pageState.status === NO_FILE || pageState.status === FILE_STAGED) {
return (
<>
<div className="row">
<div className="col-12">
<VideoInput
{...{
t,
...videoProps,
onStateChange: () =>{
if (pageState.status === NO_FILE && videoProps.file)
dispatchPageState({type: "stagedFile"})
else if (pageState.status === FILE_STAGED && !videoProps.file)
dispatchPageState({type: "clearedFile"})
}
}}
/>
</div>
</div>
<div className="row py-3">
<div className="col-12">
<div className="d-flex justify-content-end gap-3">
<button
className="btn btn-primary"
disabled={pageState.status !== FILE_STAGED}
onClick={handleUploadVideo}
>
{t("button.upload")}
</button>
</div>
</div>
</div>
</>
);
}else if (pageState.status === FILE_UPLOADING){
return(
<div className="row">
<div className="col-12">
<h1>{t("message.fileUploading")}</h1>
</div>
<div className="col-12">
<Spinner />
</div>
</div>
);
}
return <ErrorMessage />;
}
@@ -0,0 +1 @@
export { default } from './StorytellerFilter';
@@ -0,0 +1,64 @@
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
export enum states{
NO_FILE,
FILE_STAGED,
FILE_SELECTED,
FILE_UPLOADING,
FILE_UPLOADED,
FILE_LOADING,
FILE_LOADED,
}
export type State = {
status: number;
mediaFileToken?: string;
mediaFile?: MediaFile;
inferenceJobToken?: string;
}
export type Action =
| {type: 'stagedFile'}
| {type: 'clearedFile'}
| {type: 'selectedFile', payload: { mediaFileToken: string}}
| {type: 'uploadFile'}
| {type: 'uploadFileSuccess', payload:{ mediaFileToken: string}}
| {type: 'loadFile'}
| {type: 'loadFileSuccess', payload:{mediaFile: MediaFile}}
export function reducer (state: State, action: Action): State {
console.log(action);
switch(action.type){
case 'stagedFile':
return {...state, status: states.FILE_STAGED}
case 'clearedFile':
return {...state, status: states.NO_FILE}
case 'selectedFile':
return {
...state,
status: states.FILE_SELECTED,
mediaFileToken: action.payload.mediaFileToken
}
case 'uploadFile':
return {...state,status: states.FILE_UPLOADING};
case 'uploadFileSuccess':
return {
...state,
status: states.FILE_UPLOADED,
mediaFileToken: action.payload.mediaFileToken
}
case 'loadFile':
return {...state, status: states.FILE_LOADING}
case 'loadFileSuccess':
console.log("LOAD FILE SUCCESS");
return {
...state,
status: states.FILE_LOADED,
mediaFile: action.payload.mediaFile
}
default:
return {status: states.NO_FILE};
}
}
@@ -6,9 +6,8 @@ import {
InferenceJob,
} from "@storyteller/components/src/jobs/InferenceJob";
import { BasicVideo, Button, Container, Panel } from "components/common";
import { BasicVideo, Button, Container, Panel, Tabs } from "components/common";
import PageHeader from "components/layout/PageHeader";
import Tabs from "components/common/Tabs";
import { useInferenceJobs, useLocalize } from "hooks";
import TabContentUpload from "./components/tabContentUpload";
@@ -130,7 +129,9 @@ export default function VideoMotionCapture(props: {
icon={faArrowRotateLeft}
iconFlip={true}
label="Generate Another"
onClick={() => {}} //back to first state
onClick={() => {
dispatchPageState({type:"restart"})
}} //back to first state
variant="primary"
/>
</div>
@@ -21,8 +21,8 @@ export default function PageVideoMocapProgress({
pageState: State;
dispatchPageState: (action: Action) => void;
}) {
const { FILE_UPLOADING, FILE_UPLOADED, MOCAPNET_ENQUEUEING } = states;
const { FILE_UPLOADING, FILE_UPLOADED, FILE_SELECTED, MOCAPNET_ENQUEUEING } = states;
const handleEnqueueMocapNet = () => {
if (pageState.mediaFileToken) {
const request = {
@@ -43,6 +43,10 @@ export default function PageVideoMocapProgress({
}
};
if (pageState.status === FILE_SELECTED){
handleEnqueueMocapNet();
}
if (pageState.status === FILE_UPLOADING) {
return (
<div className="d-flex flex-column p-4 gap-3 text-center align-items-center">
@@ -1,17 +1,33 @@
import SelectModal from "components/common/SelectModal/SelectModal";
import React from "react";
import React, {useState} from "react";
import { Action, State } from "../videoMocapReducer";
import { Button } from "components/common";
export default function TabContentLibrary(props: {
export default function TabContentLibrary({
t, pageState, dispatchPageState
}: {
t: Function;
pageState: State;
dispatchPageState: (action: Action) => void;
}){
const [token, setToken] = useState<string|undefined>();
const handleProceed = ()=>{
if(token)
dispatchPageState({
type: "selectedFile",
payload: {mediaFileToken: token}
});
}
const handleOnSelect = (token:string)=>{
setToken(token);
};
return (
<div>
<SelectModal
modalTitle="Select a Video"
label="Select a Video"
onSelect={handleOnSelect}
tabs={[
{
label: "All Videos",
@@ -22,6 +38,12 @@ export default function TabContentLibrary(props: {
},
]}
/>
{token &&
<Button
label={t("button.proceed")}
onClick={handleProceed}
/>
}
</div>
);
}
@@ -4,6 +4,7 @@ export enum states{
FILE_STAGED,
FILE_UPLOADING,
FILE_UPLOADED,
FILE_SELECTED,
MOCAPNET_ENQUEUEING,
MOCAPNET_ENQUEUED,
}
@@ -15,20 +16,29 @@ export type State = {
}
export type Action =
| {type: 'uploadFile'}
| {type: 'restart'}
| {type: 'stagedFile'}
| {type: 'clearedFile'}
| {type: 'selectedFile', payload:{ mediaFileToken: string}}
| {type: 'uploadFile'}
| {type: 'uploadFileSuccess', payload:{ mediaFileToken: string}}
| {type: 'enqueueMocapNet'}
| {type: 'enqueueMocapNetSuccess', payload: {inferenceJobToken: string|undefined}}
export function reducer (state: State, action: Action): State {
console.log(action);
switch(action.type){
case 'restart':
return { status: states.NO_FILE }
case 'stagedFile':
return {...state, status: states.FILE_STAGED}
case 'clearedFile':
return {...state, status: states.NO_FILE}
case 'selectedFile':
return {
...state,
status: states.FILE_SELECTED,
mediaFileToken: action.payload.mediaFileToken
}
case 'uploadFile':
return {...state,status: states.FILE_UPLOADING};
case 'uploadFileSuccess':