Merge branch 'feature/mvp' of https://github.com/storytold/storyteller-frontend into feature/mvp

This commit is contained in:
Victor committed 2024-03-13 16:46:34 -07:00
commit 0c81ef7a49
24 files changed
+255 -118

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -26,6 +26,12 @@ interface Scene3DProps {
// CSS classes to apply to the engine
className?: string;
// Callback when the scene is first loaded
// Not always 100% true. Sometimes it prematurely fires.
onSceneReadyCallback?: () => void;
// Callback called when the scene is saved.
// It will receive the argument of the new scene media token.
onSceneSavedCallback?: (mediaToken: string) => void;
}
@@ -35,6 +41,7 @@ export default function Scene3D({
skybox,
fullScreen = false,
className,
onSceneReadyCallback,
onSceneSavedCallback,
}: Scene3DProps) {
@@ -54,6 +61,10 @@ export default function Scene3D({
console.log("studio-ready message (2)");
if (onSceneReadyCallback !== undefined) {
onSceneReadyCallback();
}
// NB: Example of how to call the API in the other direction:
// studio.postMessage("save-scene", engineBaseUrl);
} else if (
@@ -70,7 +81,7 @@ export default function Scene3D({
} else if (event.data === "scene-save-failed") {
console.error("Failed to save the scene!");
}
}, [onSceneSavedCallback]);
}, [onSceneReadyCallback, onSceneSavedCallback]);
useEffect(() => {
@@ -129,6 +129,7 @@
bottom: 0;
left: 0;
font-size: 14px;
text-shadow: 0px 0px 5px rgba(0, 0, 0, 0.5);
}
&::after {
@@ -140,14 +141,12 @@
height: 50%;
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
z-index: 2;
height: 35%;
}
&.selected::after {
background: linear-gradient(
to top,
rgba(desaturate(darken($primary, 20%), 30%), 70%),
transparent
);
background: linear-gradient(to top, rgba(#632323, 90%), transparent);
height: 35%;
}
}
@@ -118,18 +118,19 @@ export const VideoQuickTrim = memo(({
]); //END videoRefCallback\
useEffect(()=>{
if( compState.trimStartSeconds
&& compState.trimEndSeconds
if( compState.trimStartSeconds !== undefined
&& compState.trimEndSeconds !== undefined
&& (
compState.trimStartSeconds !== propsTrimStartSeconds
|| compState.trimEndSeconds !== propsTrimEndSeconds
)
){
onSelect({
trimStartSeconds: compState.trimStartSeconds,
trimEndSeconds: compState.trimEndSeconds,
});
}
console.log(`TRIM on useEFFECT: calling onSELECT`);
onSelect({
trimStartSeconds: compState.trimStartSeconds,
trimEndSeconds: compState.trimEndSeconds,
});
}
}, [onSelect, compState.trimStartSeconds, compState.trimEndSeconds, propsTrimStartSeconds, propsTrimEndSeconds])
function videoCanPlay(){
@@ -43,6 +43,8 @@ function LandingPage(props: Props) {
const isLoggedIn = props.sessionWrapper.isLoggedIn();
const canAccessStudio = props.sessionWrapper.canAccessStudio();
return (
<>
{domain.website === Website.StorytellerAi && !isLoggedIn && (
@@ -79,7 +81,7 @@ function LandingPage(props: Props) {
{/* STORYTELLER,AI */}
{!isLoggedIn ? (
<>
<OnboardingSelection />
{!canAccessStudio && <OnboardingSelection />}
<VstSectionV2 />
<TtsDemoSection
sessionWrapper={props.sessionWrapper}
@@ -1,5 +1,5 @@
import { Badge, Panel } from "components/common";
import React, { useEffect, useRef, useState } from "react";
import React, { useState } from "react";
import "./VstSectionV2.scss";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
@@ -22,39 +22,40 @@ interface VstSectionV2Props {}
export default function VstSectionV2(props: VstSectionV2Props) {
const [thumbsSwiper, setThumbsSwiper] = useState<SwiperType | null>(null);
const [activeIndex, setActiveIndex] = useState(0);
const [isPlaying, setIsPlaying] = useState(true);
// const [isPlaying, setIsPlaying] = useState(true);
// const togglePlayback = () => {
// setIsPlaying(!isPlaying);
// };
const handleVideoPlay = () => {
if (!isPlaying) {
setIsPlaying(true);
}
};
// const handleVideoPlay = () => {
// if (!isPlaying) {
// setIsPlaying(true);
// }
// };
const handleVideoPause = () => {
if (isPlaying) {
setIsPlaying(false);
}
};
// const handleVideoPause = () => {
// if (isPlaying) {
// setIsPlaying(false);
// }
// };
const videoRef = useRef<HTMLVideoElement | null>(null);
// const videoRef = useRef<HTMLVideoElement | null>(null);
useEffect(() => {
if (videoRef.current) {
if (isPlaying) {
videoRef.current.play();
} else {
videoRef.current.pause();
}
}
}, [isPlaying]);
// useEffect(() => {
// if (videoRef.current) {
// if (isPlaying) {
// videoRef.current.play();
// } else {
// videoRef.current.pause();
// }
// }
// }, [isPlaying]);
const videoSet = [
{
src: "/videos/landing/vst/1-original.mp4",
thumbnail: "/videos/landing/vst/1-original.jpg",
styledVideos: [
{
src: "/videos/landing/vst/1-2d_flat_anime.mp4",
@@ -80,6 +81,7 @@ export default function VstSectionV2(props: VstSectionV2Props) {
},
{
src: "/videos/landing/vst/2-original.mp4",
thumbnail: "/videos/landing/vst/2-original.jpg",
styledVideos: [
{
src: "/videos/landing/vst/2-2d_flat_anime.mp4",
@@ -157,16 +159,17 @@ export default function VstSectionV2(props: VstSectionV2Props) {
muted
playsInline
controls={false}
preload="auto"
key={`${index}-${activeIndex}`}
onPlay={handleVideoPlay}
onPause={handleVideoPause}
ref={videoRef => {
if (videoRef && isPlaying) {
videoRef.play();
} else if (videoRef) {
videoRef.pause();
}
}}
// onPlay={handleVideoPlay}
// onPause={handleVideoPause}
// ref={videoRef => {
// if (videoRef && isPlaying) {
// videoRef.play();
// } else if (videoRef) {
// videoRef.pause();
// }
// }}
>
<source src={video.src} type="video/mp4" />
Your browser does not support the video tag.
@@ -186,10 +189,10 @@ export default function VstSectionV2(props: VstSectionV2Props) {
{videoSet.map((video, index) => (
<SwiperSlide key={index}>
<div className="ratio ratio-16x9 vst-carousel-panel">
<video loop muted playsInline controls={false}>
<source src={video.src} type="video/mp4" />
Your browser does not support the video tag.
</video>
<img
src={video.thumbnail}
alt="Demo Original Video Thumbnail"
/>
</div>
</SwiperSlide>
))}
@@ -218,16 +221,17 @@ export default function VstSectionV2(props: VstSectionV2Props) {
muted
playsInline
controls={false}
preload="auto"
key={styledVideo.src}
onPlay={handleVideoPlay}
onPause={handleVideoPause}
ref={videoRef => {
if (videoRef && isPlaying) {
videoRef.play();
} else if (videoRef) {
videoRef.pause();
}
}}
// onPlay={handleVideoPlay}
// onPause={handleVideoPause}
// ref={videoRef => {
// if (videoRef && isPlaying) {
// videoRef.play();
// } else if (videoRef) {
// videoRef.pause();
// }
// }}
>
<source src={styledVideo.src} type="video/mp4" />
Your browser does not support the video tag.
@@ -10,31 +10,31 @@ export default function OnboardingSelection(props: OnboardingSelectionProps) {
const cards = [
{
text: "Scene 1",
backgroundImage: "",
text: "Room Scene",
backgroundImage: "/images/landing/onboarding/room-scene-thumbnail.webp",
onClick: () => {
history.push("/studio-intro/");
history.push("/studio-intro/m_ejhs95fc5aybp36h4a79k7523ds6an");
},
},
{
text: "Scene 2",
backgroundImage: "",
onClick: () => {
history.push("/studio-intro/");
history.push("/studio-intro");
},
},
{
text: "Scene 3",
backgroundImage: "",
onClick: () => {
history.push("/studio-intro/");
history.push("/studio-intro");
},
},
{
text: "Scene 4",
backgroundImage: "",
onClick: () => {
history.push("/studio-intro/");
history.push("/studio-intro");
},
},
];
@@ -19,7 +19,6 @@ interface Props {
}
function StudioIntroPage(props: Props) {
const [camera,cameraSet] = useState("rotation");
const { mediaToken } = useParams<{ mediaToken: string }>();
const history = useHistory();
@@ -32,11 +31,28 @@ function StudioIntroPage(props: Props) {
// for subsequent steps of this flow.
const [savedMediaToken, setSavedMediaToken] = useState(mediaToken);
// We don't show the "next step" buttons until the engine loads.
// Unfortunately the engine sometimes fires this twice, with one instance
// being called before the scene loads. Until this is fixed, we'll count the
// number of event fires and assume we must have two calls.
const [sceneIsLoadedCount, sceneIsLoadedCountSet] = useState(0);
// If the scene is saved, we know the user must have interacted.
// This can serve as a second optional gate for enabling the next steps.
const [sceneIsSaved, sceneIsSavedSet] = useState(false);
const [camera, cameraSet] = useState("rotation");
usePrefixedDocumentTitle("Storyteller Studio");
const onSaveCallback = useCallback((sceneMediaToken: string) => {
setSavedMediaToken(sceneMediaToken);
}, [setSavedMediaToken]);
sceneIsSavedSet(true); // Just in case we missed the "scene loaded" event.
}, [setSavedMediaToken, sceneIsSavedSet]);
const onSceneReadyCallback = useCallback(() => {
sceneIsLoadedCountSet(sceneIsLoadedCount + 1);
}, [sceneIsLoadedCount, sceneIsLoadedCountSet])
if (!props.sessionWrapper.canAccessStudio()) {
return <StudioNotAvailable />;
@@ -84,6 +100,30 @@ function StudioIntroPage(props: Props) {
value: "static"
}];
const sceneIsLoaded = sceneIsLoadedCount > 1 || sceneIsSaved;
let progressButtons = <></>;
if (sceneIsLoaded) {
progressButtons = (
<>
<div {...{ className: "studio-intro-exporter" }}>
<div {...{ className: "exporter-title" }}>
<span>
Add camera motion to render
</span>
</div>
<div {...{ className: "exporter-controls" }}>
<SegmentButtons {...{ onChange: ({ target }: { target: any }) => {
cameraSet(target.value)
}, options: cameraOpts, value: camera }}/>
<Button label="Create Movie from 3D Scene" onClick={onClick} />
</div>
</div>
</>
)
}
return (
<div className="studio-intro-page">
<Scene3D
@@ -91,21 +131,11 @@ function StudioIntroPage(props: Props) {
mode={EngineMode.Studio}
asset={assetDescriptor}
className="flex-grow-1"
onSceneReadyCallback={onSceneReadyCallback}
onSceneSavedCallback={onSaveCallback}
/>
<div {...{ className: "studio-intro-exporter" }}>
<div {...{ className: "exporter-title" }}>
<span>
Add camera motion to render
</span>
</div>
<div {...{ className: "exporter-controls" }}>
<SegmentButtons {...{ onChange: ({ target }: { target: any }) => {
cameraSet(target.value)
}, options: cameraOpts, value: camera }}/>
<Button label="Create Movie from 3D Scene" onClick={onClick} />
</div>
</div>
{progressButtons}
</div>
);
}
@@ -75,23 +75,23 @@ export default function PageVSTApp() {
const styleOptions = [
{
label: "Anime",
imageUrl: "/images/dummy-image.jpg",
label: "2D Anime",
imageUrl: "/images/landing/onboarding/styles/style-2d-anime.webp",
token: "weight_yqexh77ntqyawzgh9fzash798",
},
{
label: "Pixel",
imageUrl: "/images/dummy-image-2.jpg",
label: "3D Cartoon",
imageUrl: "/images/landing/onboarding/styles/style-3d-cartoon.webp",
token: "weight_yqexh77ntqyawzgh9fzash798",
},
{
label: "Painting",
imageUrl: "/images/dummy-image-3.jpg",
label: "Ink B&W",
imageUrl: "/images/landing/onboarding/styles/style-ink-bw.webp",
token: "weight_yqexh77ntqyawzgh9fzash798",
},
{
label: "Ink",
imageUrl: "/images/dummy-image-4.jpg",
label: "Origami",
imageUrl: "/images/landing/onboarding/styles/style-origami.webp",
token: "weight_yqexh77ntqyawzgh9fzash798",
},
];
@@ -18,6 +18,7 @@ export function TableOfKeyValues(
title: string;
keyValues:{
[key:string]:number|string|boolean|undefined
|{ [key:string]:number|string}
};
height?: number | string
}){
@@ -33,7 +34,9 @@ export function TableOfKeyValues(
<tr><th>{title}</th></tr>
{
Object.entries(keyValues).map(([key, val], index: number)=>{
return <tr key={index}><td>{`${key}`}</td><td>{`${val}`}</td></tr>
if(key!=="defaultCN" && key!=="defaultPrompts")
return <tr key={index}><td>{`${key}`}</td><td>{`${val}`}</td></tr>
else return null
})
}
</tbody></table>);
@@ -25,18 +25,20 @@ import {
fullbodyPreset,
landscapePreset,
typogPreset,
defaultPreset,
// defaultPreset,
} from "./dataCnPresets";
export default function CompAdvanceOptions({
debug,
debug: propsDebug = false,
t,
vstValues: vstVal,
onChange: handleOnChange,
currDefaultCN: defaultPreset
}: {
debug?: boolean;
t: Function;
vstValues: VSTType;
currDefaultCN: {[key: string]: number};
onChange: (val: {
[key: string]: number | string | boolean | undefined;
}) => void;
@@ -8,6 +8,7 @@ import { SelectModalV2 } from "components/common/SelectModal";
import { WeightCategory } from "@storyteller/components/src/api/_common/enums/WeightCategory";
import styleModels from './dataStyleModels';
import { defaultPreset as noStyleCNPreset } from './dataCnPresets';
export default function CompStyleModal({
debug,
@@ -19,7 +20,8 @@ export default function CompStyleModal({
t: Function;
value: string;
onChange: (val: {
[key: string]: number | string | boolean | undefined;
[key: string]: number | string | boolean | undefined
| {[key: string]: number | string };
}) => void;
}){
const gridContainerRef = useRef<HTMLDivElement | null>(null);
@@ -34,6 +36,18 @@ export default function CompStyleModal({
onClear={()=>onChange({
sdModelToken: "",
sdModelTitle: "",
defaultCN: noStyleCNPreset,
defaultPrompts:{
positivePrompt:"",
negativePrompt:"",
positivePromptHidden:"",
negativePromptHidden:"",
},
...noStyleCNPreset,
positivePrompt:"",
negativePrompt:"",
positivePromptHidden:"",
negativePromptHidden:"",
})}
>
<MasonryGrid
@@ -46,11 +60,20 @@ export default function CompStyleModal({
type: "weights",
inSelectModal: true,
onResultSelect: (data: any)=>{
onChange({
sdModelToken:data.token,
sdModelTitle: data.title,
})
setOnSelectTimeStamp(new Date());
const selectedStyle = styleModels.find(
(styleItem)=>styleItem.weight_token = data.token
);
if(selectedStyle!==undefined){
onChange({
sdModelToken:data.token,
sdModelTitle: data.title,
defaultCN: selectedStyle.defaultCN,
defaultPrompts: selectedStyle.defaultPrompts,
...selectedStyle.defaultCN,
...selectedStyle.defaultPrompts
});
setOnSelectTimeStamp(new Date());
}
}
};
//console.log(data);
@@ -10,13 +10,13 @@ type CNPreset =
const defaultPreset = {
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0.7,
cnLineArtAnime: 0,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 1,
cnOpenPose: 0,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 1,
cnSoftEdge: 0,
cnRegularSteps: 20,
}
@@ -1,7 +1,9 @@
import { defaultPreset as defaultCN } from "./dataCnPresets";
// import { defaultPreset as defaultCN } from "./dataCnPresets";
import styleModels from "./dataStyleModels";
import { VSTType } from "./helpers";
export {initialValues, hiddenValues};
const initialValues = {
const initialValues: VSTType = {
//files Settings
fileToken: "",
outputPath: "vid2vid/SparseUpscaleInterp_00001.mp4",
@@ -17,17 +19,33 @@ const initialValues = {
workflowConfig: "weight_0a04e66y14t4e2bwxjfyg1mb2",
sdModelToken: styleModels[0].weight_token,
sdModelTitle: styleModels[0].title,
loraModelToken: "",
loraModelStrength: 0,
// loraModelToken: "",
// loraModelStrength: 0,
//basics
inputFps: 24,
posPrompt: "perfect anime illustration,(best-quality:0.8),",
negPrompt: "",
...styleModels[0].defaultPrompts,
// positivePrompt:"",
// negativePrompt:"",
// positivePromptHidden:"",
// negativePromptHidden:"",
visibility: "private",
//Control Net
...defaultCN
...styleModels[0].defaultCN,
// cnCanny: 0,
// cnDepth: 0,
// cnLineArtAnime: 0,
// cnLineArtRealistic: 0,
// cnLipsStrength: 0,
// cnOpenPose: 0,
// cnPipeFace: 0,
// cnSparseScribble: 0,
// cnSoftEdge: 0,
// cnRegularSteps: 0,
defaultCN:styleModels[0].defaultCN,
defaultPrompts:styleModels[0].defaultPrompts,
};
const hiddenValues = {
@@ -33,7 +33,25 @@ const styleModels = [
"bookmark_count": 1
},
"created_at": "2024-02-28T21:00:43Z",
"updated_at": "2024-02-28T21:00:43Z"
"updated_at": "2024-02-28T21:00:43Z",
defaultCN:{
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 1,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 1,
cnRegularSteps: 20,
},
defaultPrompts:{
positivePrompt:"perfect anime illustration,(best-quality:0.8),",
negativePrompt:"",
positivePromptHidden:"",
negativePromptHidden:"flare, lens flare, glare,naked, nsfw, text, logo, watermark:1.3, letterboxed,embedding:easynegative, embedding:badhandv4,(worst quality, low quality:1.4), lowres, blurry, monochrome, ",
}
},
// {
// "weight_token": "weight_vbz0wq7wkv80casj3vfptrrpg",
@@ -1,5 +1,4 @@
import { v4 as uuidv4 } from "uuid";
import { hiddenValues } from "./dataDefaultValues";
export type VSTType = {
// File Settings
@@ -21,8 +20,10 @@ export type VSTType = {
//basics
inputFps: number;
posPrompt: string;
negPrompt: string;
positivePrompt: string;
negativePrompt: string;
positivePromptHidden:string,
negativePromptHidden:string,
visibility: string;
//advance
@@ -36,6 +37,25 @@ export type VSTType = {
cnSparseScribble: number;
cnSoftEdge: number;
cnRegularSteps: number
defaultCN: {
cnCanny: number;
cnDepth: number;
cnLineArtAnime: number;
cnLineArtRealistic: number;
cnLipsStrength: number;
cnOpenPose: number;
cnPipeFace: number;
cnSparseScribble: number;
cnSoftEdge: number;
cnRegularSteps: number
},
defaultPrompts:{
positivePrompt:string,
negativePrompt:string,
positivePromptHidden:string,
negativePromptHidden:string,
}
}
export function isInputValid(keyValues: {[key:string]:number|string|boolean|undefined}){
@@ -61,9 +81,9 @@ export function mapRequest(vstValues: VSTType){
"maybe_json_modifications": {
"$.154.inputs.Value": vstValues.inputFps,
"$.510.inputs.Text":
hiddenValues.posPrompt + vstValues.posPrompt,
vstValues.positivePromptHidden + vstValues.positivePrompt,
"$.8.inputs.text":
hiddenValues.negPrompt+ vstValues.negPrompt,
vstValues.negativePromptHidden+ vstValues.negativePrompt,
"$.401.inputs.Value": 0.7, //Denoise First Pass
"$.140.inputs.Value": 1, // Every nth frame
@@ -38,7 +38,13 @@ export default function PageVSTApp({
});
const handleOnChange = (val: {
[key: string]: number | string | boolean | undefined;
[key: string]:
number |
string |
boolean |
undefined |
{[key: string]: number | string}
;
}) => {
setVstValues(curr => ({ ...curr, ...val }));
};
@@ -73,12 +79,12 @@ export default function PageVSTApp({
trimStartSeconds={vstValues.trimStart}
trimEndSeconds={vstValues.trimEnd}
mediaToken={vstValues.fileToken}
onSelect={(val: QuickTrimData) =>
onSelect={(val: QuickTrimData) =>{
handleOnChange({
trimStart: val.trimStartSeconds,
trimEnd: val.trimEndSeconds,
})
}
}}
onResponse={res => {
dispatchPageState({
type: "loadFileSuccess",
@@ -97,7 +103,7 @@ export default function PageVSTApp({
placeholder: t("input.placeholder.prompt"),
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
handleOnChange({ posPrompt: e.target.value }),
value: vstValues.posPrompt,
value: vstValues.positivePrompt,
required: false,
}}
/>
@@ -107,7 +113,7 @@ export default function PageVSTApp({
placeholder: t("input.placeholder.negPrompt"),
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
handleOnChange({ negPrompt: e.target.value }),
value: vstValues.negPrompt,
value: vstValues.negativePrompt,
required: false,
}}
/>
@@ -121,6 +127,7 @@ export default function PageVSTApp({
<CompAdvanceOptions
debug={debug}
t={t}
currDefaultCN={vstValues.defaultCN}
onChange={handleOnChange}
vstValues={vstValues}
/>
@@ -82,6 +82,5 @@ export function reducer (state: State, action: Action): State {
case 'reset':
default:
return {status: states.NO_FILE};
}
}