mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 02:00:46 +00:00
control panel ui is done
This commit is contained in:
1 parent
094cda6525
commit
3e023a5d81
6 files changed
+336
-12
No files matched your search
@@ -211,7 +211,6 @@ function initInferenceJobsByCategoryMap(): Map<
|
||||
inferenceJobsByCategory.set(FrontendInferenceJobType.ImageGeneration, []);
|
||||
inferenceJobsByCategory.set(FrontendInferenceJobType.ConvertFbxtoGltf, []);
|
||||
inferenceJobsByCategory.set(FrontendInferenceJobType.VideoWorkflow, [])
|
||||
return inferenceJobsByCategory;
|
||||
inferenceJobsByCategory.set(FrontendInferenceJobType.VideoStyleTransfer, [])
|
||||
return inferenceJobsByCategory;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, forwardRef } from "react";
|
||||
import { useMedia } from "hooks";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import makeClass from "resources/makeClass";
|
||||
|
||||
import { Label } from "components/common"
|
||||
|
||||
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
src?: string;
|
||||
mediaToken?: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export default function VideoFromFakeyou({
|
||||
type Ref = HTMLVideoElement;
|
||||
|
||||
const VideoFromFakeyou = forwardRef<Ref, Props>(({
|
||||
className,
|
||||
src,
|
||||
mediaToken,
|
||||
label,
|
||||
...rest
|
||||
}: Props) {
|
||||
}: Props, ref) => {
|
||||
const [mediaFile, setMediaFile] = useState<MediaFile>();
|
||||
useMedia({
|
||||
mediaToken: mediaToken,
|
||||
@@ -31,11 +37,14 @@ export default function VideoFromFakeyou({
|
||||
if (mediaLink){
|
||||
return (
|
||||
<div {...{ ...makeClass("fy-basic-video",className) }}>
|
||||
<video controls key={mediaToken} {...rest}>
|
||||
{label && <Label label={label}/>}
|
||||
<video controls key={mediaToken} {...rest} ref={ref}>
|
||||
<source src={mediaLink} type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null;
|
||||
};
|
||||
});
|
||||
|
||||
export default VideoFromFakeyou;
|
||||
@@ -39,6 +39,7 @@ import TextArea from "./TextArea";
|
||||
import TextAreaV2 from "./TextAreaV2";
|
||||
import TintSpinner from "./TintSpinner";
|
||||
import VideoBasic from "./VideoBasic";
|
||||
import VideoFakeyou from "./VideoFakeyou";
|
||||
import VideoInput from "./VideoInput";
|
||||
|
||||
export {
|
||||
@@ -83,5 +84,6 @@ export {
|
||||
TextAreaV2,
|
||||
TintSpinner,
|
||||
VideoBasic,
|
||||
VideoFakeyou,
|
||||
VideoInput
|
||||
};
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
import React from 'react';
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
export type WorkflowValuesType = {
|
||||
// File Settings
|
||||
fileToken: string;
|
||||
outputPath: string;
|
||||
// Video Settings
|
||||
width: number;
|
||||
height: number;
|
||||
maxFrames: number;
|
||||
framesCap: number;
|
||||
skipFrames: number;
|
||||
everyNthFrame: number;
|
||||
inputFps: number;
|
||||
interpolationMultiplier: number;
|
||||
// Basic Inputs
|
||||
workflowConfig: string;
|
||||
seed: string;
|
||||
sdModelToken: string;
|
||||
loraModelToken: string;
|
||||
posPrompt: string;
|
||||
negPrompt: string;
|
||||
// Advance Options Values
|
||||
firstPass: number;
|
||||
upscalePass: number;
|
||||
motionScale: number;
|
||||
upscaleMultiplier: number;
|
||||
useEmptyLatent: boolean;
|
||||
useFaceDetailer: boolean;
|
||||
denoiseFaceDetailer: number;
|
||||
useLCM: boolean;
|
||||
lcmCFG: number;
|
||||
lcmSteps: number;
|
||||
// Control Net Values
|
||||
cnCanny: number;
|
||||
cnDepth: number;
|
||||
cnLinearAnime: number;
|
||||
cnLinearRealistic: number;
|
||||
cnOpenPose: number;
|
||||
cnPipeFace: number;
|
||||
cnSparse: number;
|
||||
cnTile: number;
|
||||
}
|
||||
export function isInputValid(keyValues: {[key:string]:number|string|boolean|undefined}){
|
||||
return (
|
||||
keyValues.sdModelToken
|
||||
&& keyValues.sdModelToken !== ""
|
||||
&& keyValues.posPrompt
|
||||
&& keyValues.posPrompt !== ""
|
||||
);
|
||||
}
|
||||
|
||||
export function mapRequest(workflowValues: WorkflowValuesType){
|
||||
//TODO: improve the typing of this
|
||||
return {
|
||||
"uuid_idempotency_token": uuidv4(),
|
||||
"maybe_sd_model": workflowValues.sdModelToken,
|
||||
"maybe_workflow_config": workflowValues.workflowConfig,
|
||||
"maybe_input_file": workflowValues.fileToken,
|
||||
"maybe_output_path": workflowValues.outputPath,
|
||||
"maybe_json_modifications": {
|
||||
"$.510.inputs.Text": workflowValues.posPrompt,
|
||||
"$.8.inputs.text": workflowValues.negPrompt,
|
||||
"$.173.inputs.seed": workflowValues.seed,
|
||||
"$.401.inputs.Value": workflowValues.firstPass,
|
||||
"$.918.inputs.Value": workflowValues.motionScale,
|
||||
"$.137.inputs.Value": workflowValues.framesCap,
|
||||
"$.186.inputs.Value": workflowValues.skipFrames,
|
||||
"$.140.inputs.Value": workflowValues.everyNthFrame,
|
||||
"$.154.inputs.Value": workflowValues.inputFps,
|
||||
"$.445.inputs.number": workflowValues.interpolationMultiplier,
|
||||
"$.947.inputs.Value": workflowValues.cnTile,
|
||||
"$.800.inputs.Value": workflowValues.cnCanny,
|
||||
"$.797.inputs.Value": workflowValues.cnLinearAnime,
|
||||
"$.796.inputs.Value": workflowValues.cnLinearRealistic,
|
||||
"$.772.inputs.Value": workflowValues.cnDepth,
|
||||
"$.771.inputs.Value": workflowValues.cnOpenPose,
|
||||
"$.527.inputs.Value": workflowValues.cnPipeFace,
|
||||
"$.403.inputs.Value": workflowValues.cnSparse
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function TableOfKeyValues(
|
||||
{
|
||||
keyValues,
|
||||
maxHeight=500
|
||||
}:{
|
||||
keyValues:{
|
||||
[key:string]:number|string|boolean|undefined
|
||||
};
|
||||
maxHeight?: number
|
||||
}){
|
||||
return(<table style={{
|
||||
maxHeight: maxHeight,
|
||||
overflow: "scroll",
|
||||
border: "1px solid white",
|
||||
borderRadius: "1rem",
|
||||
}}>{
|
||||
Object.entries(keyValues).map(([key, val], index: number)=>{
|
||||
return <tr key={index}><td>{`${key}`}</td><td>{`${val}`}</td></tr>
|
||||
})
|
||||
}</table>);
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
const initialValues = {
|
||||
//config
|
||||
outputPath: "vid2vid/SparseUpscaleInterp_00001.mp4",
|
||||
workflowConfig: "weight_q8sz47gmfw2zx02snrbz88ns9",
|
||||
|
||||
//video Settings
|
||||
width: 544,
|
||||
height: 544,
|
||||
maxFrames: 17,
|
||||
framesCap: 16,
|
||||
skipFrames: 0,
|
||||
everyNthFrame: 2,
|
||||
inputFps: 24,
|
||||
interpolationMultiplier: 2,
|
||||
|
||||
//basics
|
||||
seed: "",
|
||||
sdModelToken: "",
|
||||
loraModelToken: "",
|
||||
posPrompt: "",
|
||||
negPrompt: "",
|
||||
|
||||
//advance
|
||||
firstPass: 1,
|
||||
upscalePass: 0.42,
|
||||
motionScale:1,
|
||||
upscaleMultiplier: 1.5,
|
||||
useEmptyLatent: false,
|
||||
useFaceDetailer: false,
|
||||
denoiseFaceDetailer: 0.45,
|
||||
useLCM: false,
|
||||
lcmCFG: 2,
|
||||
lcmSteps: 8,
|
||||
|
||||
//Control Net
|
||||
cnCanny: 0,
|
||||
cnDepth: 0,
|
||||
cnLinearAnime: 0,
|
||||
cnLinearRealistic: 0,
|
||||
cnOpenPose: 0,
|
||||
cnPipeFace: 0,
|
||||
cnSparse: 0.7,
|
||||
cnTile: 0,
|
||||
};
|
||||
export default initialValues;
|
||||
+170
-6
@@ -1,13 +1,29 @@
|
||||
import React from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
|
||||
import { useParams, useHistory } from "react-router-dom";
|
||||
import { useMedia } from "hooks";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
|
||||
import { EnqueueVideoWorkflow } from "@storyteller/components/src/api/video_workflow";
|
||||
import { states, Action, State } from "../../reducer";
|
||||
import ivs from "./initialValues";
|
||||
import {
|
||||
isInputValid,
|
||||
mapRequest,
|
||||
WorkflowValuesType,
|
||||
TableOfKeyValues
|
||||
} from "./helpers";
|
||||
|
||||
export default function pageVSTApp({
|
||||
debug, t, pageState, dispatchPageState, parentPath
|
||||
import {
|
||||
Button,
|
||||
Label,
|
||||
NumberSliderV2,
|
||||
Panel,
|
||||
SelectModal,
|
||||
TextArea,
|
||||
VideoFakeyou
|
||||
} from "components/common";
|
||||
|
||||
export default function PageVSTApp({
|
||||
debug=true, t, pageState, dispatchPageState, parentPath
|
||||
}: {
|
||||
debug?: boolean;
|
||||
t: Function;
|
||||
@@ -15,7 +31,155 @@ export default function pageVSTApp({
|
||||
parentPath: string;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
const { mediaToken } = useParams<any>();
|
||||
|
||||
const [workflowValues, setWorkflowValues] = useState<WorkflowValuesType>({
|
||||
fileToken: pageState.mediaFileToken || mediaToken,
|
||||
...ivs
|
||||
});
|
||||
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
if(videoRef?.current){
|
||||
const ve = videoRef.current
|
||||
ve.onloadedmetadata = () =>{
|
||||
if (ve.videoWidth && ve.videoHeight) {
|
||||
const aspectRatio = ve.videoWidth/ve.videoHeight
|
||||
if (aspectRatio > 1){
|
||||
handleOnChange("width",960);
|
||||
}
|
||||
else if (aspectRatio < 1) {
|
||||
handleOnChange("height",960);
|
||||
}else{
|
||||
if (debug) console.log(`aspectRaio: ${aspectRatio}`);
|
||||
}
|
||||
}
|
||||
if(ve.duration){
|
||||
handleOnChange("maxFrames",
|
||||
Math.floor(ve.duration)*workflowValues.inputFps
|
||||
);
|
||||
}
|
||||
//TODO: Optimizer to make ONE handleOnChange only
|
||||
//TODO: deal with maxFrames with more reliable math
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const handleOnChange = (key: string, newValue:any,) => {
|
||||
setWorkflowValues((curr)=>({...curr, [key]: newValue}));
|
||||
}
|
||||
const history = useHistory();
|
||||
const handleGenerate = ()=>{
|
||||
if(debug) console.log(workflowValues)
|
||||
|
||||
if (isInputValid(workflowValues)){
|
||||
const request = mapRequest(workflowValues);
|
||||
console.log(request);
|
||||
EnqueueVideoWorkflow(request).then(res => {
|
||||
if (res.success && res.inference_job_token) {
|
||||
dispatchPageState({
|
||||
type: 'enqueueFilterSuccess',
|
||||
payload: {
|
||||
inferenceJobToken: res.inference_job_token
|
||||
}
|
||||
})
|
||||
}else{
|
||||
console.log(res);
|
||||
}
|
||||
})
|
||||
dispatchPageState({type: 'enqueueFilter'})
|
||||
history.push(`${parentPath}/jobs`);
|
||||
}else{
|
||||
alert("you must pick an sd weight and input a prompt");
|
||||
}
|
||||
}
|
||||
|
||||
return(
|
||||
<p>hi</p>
|
||||
<Panel className="mb-4 p-4">
|
||||
<div className="row g-3 mb-4">
|
||||
<div className="col-12 col-md-6">
|
||||
<VideoFakeyou
|
||||
label={t("video.label.original")}
|
||||
mediaToken={workflowValues.fileToken}
|
||||
ref={videoRef}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<Label label={t("image.label.preview")}/>
|
||||
{debug && <p><TableOfKeyValues keyValues={workflowValues}/></p>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 mb-4">
|
||||
<div className="col-12 col-md-6">
|
||||
<SelectModal
|
||||
modalTitle={t("modal.title.selectStyle")}
|
||||
label={t("input.label.selectStyle")}
|
||||
onSelect={({token})=>{
|
||||
handleOnChange("sdModelToken", token);
|
||||
}}
|
||||
tabs={[
|
||||
{
|
||||
label: "All Weights",
|
||||
tabKey: "allWeights",
|
||||
typeFilter: "sd_1.5",
|
||||
searcher: true,
|
||||
type: "weights",
|
||||
},
|
||||
{
|
||||
label: "Bookmarked",
|
||||
tabKey: "bookmarkedWeights",
|
||||
typeFilter: "sd_1.5",
|
||||
searcher: false,
|
||||
type: "weights",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<br/>
|
||||
<NumberSliderV2 {...{
|
||||
min: 1, max: 10, step: 1,
|
||||
initialValue: workflowValues.upscaleMultiplier,
|
||||
label: t("input.label.styleStrength"),
|
||||
thumbTip: "24 frames = 1 sec",
|
||||
withRevert:true,
|
||||
onChange: (val)=>{handleOnChange("upscaleMultiplier",val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<TextArea
|
||||
{...{
|
||||
label: t("input.label.prompt"),
|
||||
placeholder: t("input.placeholder.prompt"),
|
||||
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange("posPrompt", e.target.value),
|
||||
value: workflowValues.posPrompt,
|
||||
required: false,
|
||||
}}
|
||||
/>
|
||||
<br/>
|
||||
<br/>
|
||||
<TextArea
|
||||
{...{
|
||||
label: t("input.label.negPrompt"),
|
||||
placeholder: t("input.placeholder.negPrompt"),
|
||||
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange("negPrompt", e.target.value),
|
||||
value: workflowValues.negPrompt,
|
||||
required: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3">
|
||||
<div className="col-12 d-flex justify-content-between">
|
||||
<Button
|
||||
label={t("button.cancel")}
|
||||
// onClick={handleGenerate}
|
||||
variant="primary"
|
||||
/>
|
||||
<Button
|
||||
label={t("button.enqueueWorkflow")}
|
||||
onClick={handleGenerate}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user