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

This commit is contained in:
BFlat committed 2024-01-31 15:08:14 +07:00
commit ac7cbe5aca
18 files changed
+233 -72

No files matched your search

@@ -2,7 +2,7 @@ import { ApiConfig } from "../ApiConfig";
export interface EnqueueImageGenRequest {
uuid_idempotency_token: string;
maybe_sd_model_token: string;
maybe_sd_model_token: string | undefined;
maybe_lora_model_token: string | null;
maybe_prompt: string;
maybe_n_prompt: string;
Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

+1
View File
@@ -204,6 +204,7 @@ function initInferenceJobsByCategoryMap(): Map<
);
inferenceJobsByCategory.set(FrontendInferenceJobType.VoiceDesignerTts, []);
inferenceJobsByCategory.set(FrontendInferenceJobType.VideoMotionCapture, []);
inferenceJobsByCategory.set(FrontendInferenceJobType.ImageGeneration, []);
inferenceJobsByCategory.set(FrontendInferenceJobType.ConvertFbxtoGltf, []);
return inferenceJobsByCategory;
}
@@ -5,6 +5,7 @@
background-color: $panel-inner;
pointer-events: none;
overflow: hidden;
box-shadow: none !important;
.overlay {
position: absolute;
@@ -20,7 +21,7 @@
font-size: 16px;
pointer-events: all;
text-shadow: 0 0 10px rgba(0, 0, 0, 1);
border: 1px solid rgba($primary, 0.75);
border: 1px solid rgba($primary, 0.4);
border-radius: 0.5rem;
}
}
@@ -43,13 +43,13 @@ export default function PremiumLock({
{hasAccess() ? (
children
) : (
<Panel padding={true} className="fy-premium-lock rounded">
<Panel className="fy-premium-lock rounded px-3 py-4">
{children}
<div className="overlay">
<div className="d-flex flex-column align-items-center gap-2">
<div className="d-flex flex-column align-items-center gap-2 text-center">
<FontAwesomeIcon icon={faLock} className="me-2 fs-5" />
<div>
Feature requires{" "}
This feature requires a{" "}
<Link to="/pricing">{requiredPlan} subscription</Link>
</div>
</div>
@@ -21,7 +21,9 @@ export default function Label({
}}
>
{label ? (
<label {...{ className: `${required ? " required" : ""}`.trim() }}>
<label
{...{ className: `fw-medium ${required ? " required" : ""}`.trim() }}
>
{label}
</label>
) : null}
@@ -12,7 +12,7 @@ import SelectWeightsList from "./SelectWeightsList";
interface TabConfig {
label: string;
tabKey: string;
type?: "media" | "weights";
type: "media" | "weights";
weightTypeFilter?: string;
mediaTypeFilter?: string;
searcher?: boolean;
@@ -21,11 +21,18 @@ interface SelectModalProps {
label?: string;
tabs: TabConfig[];
modalTitle?: string;
onSelect?: (token: string)=>void;
onSelect?: (token: string) => void;
required?: boolean;
}
const SelectModal = memo(
({ label, tabs, modalTitle = "Select", onSelect }: SelectModalProps) => {
({
label,
tabs,
modalTitle = "Select",
onSelect,
required,
}: SelectModalProps) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const { token, setToken, weightTitle, setWeightTitle } = useToken();
const [selectedValue, setSelectedValue] = useState("");
@@ -41,18 +48,21 @@ const SelectModal = memo(
useEffect(() => {
const currentTab = tabs.find(tab => tab.tabKey === activeTab);
setMediaType(currentTab?.mediaTypeFilter || "all");
setWeightType(currentTab?.mediaTypeFilter || "all");
setWeightType(currentTab?.weightTypeFilter || "all");
}, [activeTab, tabs]);
useEffect(()=>{
if(token && token !== selectedValue && onSelect){
useEffect(() => {
if (token && token !== selectedValue && onSelect) {
setSelectedValue(token);
onSelect(token);
}else if(weightTitle && weightTitle !== selectedValue && onSelect){
} else if (weightTitle && weightTitle !== selectedValue && onSelect) {
setSelectedValue(weightTitle);
onSelect(weightTitle);
}
}, [token, weightTitle, selectedValue, onSelect]);
console.log("token", token, "weightTitle", weightTitle);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token, weightTitle]);
const openModal = () => {
setIsModalOpen(true);
@@ -101,7 +111,11 @@ const SelectModal = memo(
return (
<>
<div>
{label && <label className="sub-title">{label}</label>}
{label && (
<label className={`sub-title ${required && "required"}`.trim()}>
{label}
</label>
)}
<div className="d-flex gap-2">
<Input
@@ -14,6 +14,7 @@ import {
faSignOutAlt,
faCameraMovie,
faPersonRays,
faMessageImage,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useEffect, useState } from "react";
@@ -341,7 +342,7 @@ export default function SideNav({
{"Voice Designer"}
</NavLink>
</li>
<hr className="mb-4 mt-3" />
<hr className="mb-3 mt-3" />
<li className="sidebar-heading">{t("videoTitle")}</li>
<li>
<NavLink
@@ -384,6 +385,22 @@ export default function SideNav({
{/* {t("videoStorytellerStudio")} */}
</NavLink>
</li>
<hr className="mb-3 mt-3" />
<li className="sidebar-heading">Image Generation</li>
<li>
<NavLink
to="/text-to-image"
activeClassName="active-link"
onClick={handleNavLinkClick}
>
<FontAwesomeIcon
icon={faMessageImage}
className="sidebar-heading-icon"
/>
Text to Image
{/* {t("videoStorytellerStudio")} */}
</NavLink>
</li>
<hr className="mb-3 mt-3" />
<li className="sidebar-heading">{t("communityTitle")}</li>
@@ -400,7 +417,7 @@ export default function SideNav({
{t("communityUploadModels")}
</NavLink>
</li>
<li>
<li className="mb-3">
<a
href="https://discord.gg/fakeyou"
target="_blank"
@@ -32,7 +32,8 @@ export default function useLazyLists({
const { pathname, search: locSearch } = useLocation();
const history = useHistory();
const urlQueries = new URLSearchParams(locSearch);
const [next, nextSet] = useState(urlQueries.get("cursor") || "");
const urlCursor = urlQueries.get("cursor");
const [next, nextSet] = useState(urlCursor || "");
const [previous, previousSet] = useState(""); // I am not used for anything yet :)
const [sort, sortSet] = useState(urlQueries.get("sort_ascending") === "true");
const [status, statusSet] = useState(
@@ -61,6 +62,13 @@ export default function useLazyLists({
statusSet(FetchStatus.ready);
};
const reset = () => {
listSet([]); // Reset list on filter/sort change
nextSet("");
previousSet("");
statusSet(FetchStatus.ready);
}
useEffect(() => {
const queries = {
...(next ? { cursor: next } : {}),
@@ -98,7 +106,7 @@ export default function useLazyLists({
}
});
nextSet(res.pagination.maybe_next || "");
previousSet(res.pagination.maybe_next);
previousSet(res.pagination.maybe_previous);
}
});
}
@@ -127,10 +135,12 @@ export default function useLazyLists({
next,
onChange,
previous,
reset,
sort,
sortSet,
status,
statusSet,
totalKeys,
urlCursor
};
}
@@ -1,8 +1,10 @@
type Setter = (x: any) => void
type Setter = (x: any) => void;
export default function onChanger({ ...setters }: { [key: string]: Setter }) {
return ({ target }: { target: { name: string; value: any } }) => {
const targetName =
target.name.charAt(0).toUpperCase() + target.name.slice(1);
const todo: { [key: string]: Setter } = setters;
todo[target.name + "Set"](target.value);
todo["set" + targetName](target.value);
};
}
}
@@ -330,7 +330,7 @@ styles with important tags:
0 15px 15px rgb(0 0 0 / 5%),
0 10px 8px rgb(0 0 0 / 3%),
0 4px 4px rgb(0 0 0 / 2%),
0 2px 2px rgb(0 0 0 / 1%) !important;
0 2px 2px rgb(0 0 0 / 1%);
.video-bg {
background-color: $black;
@@ -110,6 +110,7 @@ import FbxToGltfPage from "./pages/fbx_to_gltf/FbxToGltfPage";
import UploadLoraWeightPage from "./pages/upload/UploadLoraWeightPage";
import StorytellerFilterPage from "./pages/storyteller_filter/StorytellerFilter";
import ScrollToTop from "./_common/ScrollToTop";
import TextToImagePage from "./pages/text_to_image/TextToImagePage";
interface Props {
sessionWrapper: SessionWrapper;
@@ -834,6 +835,15 @@ class PageContainer extends React.Component<
/>
</Route>
<Route path="/text-to-image">
<TextToImagePage
sessionSubscriptionsWrapper={
this.props.sessionSubscriptionsWrapper
}
enqueueInferenceJob={this.props.enqueueInferenceJob}
/>
</Route>
<Route path="/character/donald-trump">
<TrumpTtsPage
sessionWrapper={this.props.sessionWrapper}
@@ -2,7 +2,7 @@ import React, { useRef, useState } from "react";
import { useLocation } from "react-router-dom";
import MasonryGrid from "components/common/MasonryGrid/MasonryGrid";
import MediaCards from "components/common/Card/MediaCards";
import { TempSelect } from "components/common";
import { Button, TempSelect } from "components/common";
import {
faArrowDownWideShort,
faFilter,
@@ -59,6 +59,8 @@ export default function MediaTab() {
// { value: "mostliked", label: "Most Liked" },
];
console.log("💎",media.urlCursor);
return (
<>
<div className="d-flex flex-wrap gap-3 mb-3">
@@ -82,6 +84,7 @@ export default function MediaTab() {
}}
/>
</div>
{ media.urlCursor ? <Button {...{ label: "Back to top", onClick: () => media.reset() }}/> : null }
</div>
<AudioPlayerProvider>
{media.isLoading && !media.list.length ? (
@@ -2,7 +2,7 @@ import React, { useRef, useState } from "react";
import { useLocation } from "react-router-dom";
import MasonryGrid from "components/common/MasonryGrid/MasonryGrid";
import WeightsCards from "components/common/Card/WeightsCards";
import { TempSelect } from "components/common";
import { Button, TempSelect } from "components/common";
import {
faArrowDownWideShort,
faFilter,
@@ -145,6 +145,7 @@ export default function WeightsTab() {
/>
)} */}
</div>
{ weights.urlCursor ? <Button {...{ label: "Back to top", onClick: () => weights.reset() }}/> : null }
</div>
<AudioPlayerProvider>
{weights.isLoading && !weights.list.length ? (
@@ -0,0 +1,51 @@
import { Container } from "components/common";
import PageHeaderWithImage from "components/layout/PageHeaderWithImage";
import React from "react";
import SdInferencePanel from "../weight/inference_panels/SdInferencePanel";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
import { faMessageImage } from "@fortawesome/pro-solid-svg-icons";
import TextToImageJobsList from "./components/TextToImageJobsList";
import { useInferenceJobs } from "hooks";
interface TextToImagePageProps {
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
enqueueInferenceJob: (
jobToken: string,
frontendInferenceJobType: FrontendInferenceJobType
) => void;
}
export default function TextToImagePage({
sessionSubscriptionsWrapper,
enqueueInferenceJob,
}: TextToImagePageProps) {
const { inferenceJobs } = useInferenceJobs(
FrontendInferenceJobType.ImageGeneration
);
const hasImageGenJobs = inferenceJobs && inferenceJobs.length > 0;
return (
<Container type="panel">
<PageHeaderWithImage
headerImage="/mascot/text-to-image.webp"
titleIcon={faMessageImage}
title="Text to Image"
subText="Transform your thoughts into art."
yOffset="76%"
/>
{hasImageGenJobs && (
<div className="mb-4">
<TextToImageJobsList />
</div>
)}
<SdInferencePanel
sessionSubscriptionsWrapper={sessionSubscriptionsWrapper}
enqueueInferenceJob={enqueueInferenceJob}
isStandalone={true}
/>
</Container>
);
}
@@ -0,0 +1,26 @@
import React from "react";
import { Analytics } from "common/Analytics";
import InferenceJobsList from "components/layout/InferenceJobsList";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
export default function TextToImageJobsList() {
const failures = (fail = "") => {
switch (fail) {
case "sample case":
return "Sample Case, this should not have been shown";
default:
return "Unknown failure";
}
};
return (
<InferenceJobsList
{...{
failures,
onSelect: () => Analytics.voiceConversionClickDownload(),
jobType: FrontendInferenceJobType.ImageGeneration,
}}
/>
);
}
@@ -1,24 +1,26 @@
import SelectModal from "components/common/SelectModal/SelectModal";
import React, {useState} from "react";
import React, { useState } from "react";
import { Action, State } from "../videoMocapReducer";
import { Button } from "components/common";
export default function TabContentLibrary({
t, pageState, dispatchPageState
t,
pageState,
dispatchPageState,
}: {
t: Function;
pageState: State;
dispatchPageState: (action: Action) => void;
}){
const [token, setToken] = useState<string|undefined>();
const handleProceed = ()=>{
if(token)
}) {
const [token, setToken] = useState<string | undefined>();
const handleProceed = () => {
if (token)
dispatchPageState({
type: "selectedFile",
payload: {mediaFileToken: token}
payload: { mediaFileToken: token },
});
}
const handleOnSelect = (token:string)=>{
};
const handleOnSelect = (token: string) => {
setToken(token);
};
@@ -38,12 +40,7 @@ export default function TabContentLibrary({
},
]}
/>
{token &&
<Button
label={t("button.proceed")}
onClick={handleProceed}
/>
}
{token && <Button label={t("button.proceed")} onClick={handleProceed} />}
</div>
);
}
@@ -2,6 +2,7 @@ import React, { useRef, useState, memo } from "react";
import {
Button,
Input,
Label,
NumberSlider,
Panel,
SegmentButtons,
@@ -28,19 +29,26 @@ import PremiumLock from "components/PremiumLock";
interface SdInferencePanelProps {
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
sd_model_token: string;
sd_model_token?: string;
enqueueInferenceJob: (
jobToken: string,
frontendInferenceJobType: FrontendInferenceJobType
) => void;
isStandalone?: boolean;
}
function SdInferencePanel({
enqueueInferenceJob,
sd_model_token,
sessionSubscriptionsWrapper,
isStandalone = false,
}: SdInferencePanelProps) {
const [loraToken, setLoraToken] = useState<string | null>(null);
const [weightToken, setWeightToken] = useState(sd_model_token);
const handleOnWeightSelect = (token: string) => {
setWeightToken(token);
};
const handleOnSelect = (token: string) => {
setLoraToken(token);
@@ -226,7 +234,7 @@ function SdInferencePanel({
) => {
ev.preventDefault();
if (!prompt) {
if (!prompt || weightToken === undefined) {
return false;
}
@@ -243,7 +251,7 @@ function SdInferencePanel({
const request = {
uuid_idempotency_token: uuidv4(),
maybe_sd_model_token: sd_model_token,
maybe_sd_model_token: isStandalone ? weightToken : sd_model_token,
maybe_lora_model_token: loraToken,
maybe_prompt: prompt,
maybe_n_prompt: negativePrompt,
@@ -282,6 +290,31 @@ function SdInferencePanel({
<h4 className="fw-semibold mb-4">Generate an Image</h4>
<div className="d-flex flex-column gap-3 mb-4">
{isStandalone && (
<SelectModal
required={true}
modalTitle="Select a Weight"
label="Select a Weight"
onSelect={handleOnWeightSelect}
tabs={[
{
label: "All Weights",
tabKey: "allWeights",
weightTypeFilter: "sdxl",
searcher: true,
type: "weights",
},
{
label: "Bookmarked",
tabKey: "bookmarkedWeights",
weightTypeFilter: "sdxl",
searcher: false,
type: "weights",
},
]}
/>
)}
<TempTextArea
{...{
label: "Prompt",
@@ -293,7 +326,7 @@ function SdInferencePanel({
/>
<TempTextArea
{...{
label: "Negative prompt",
label: "Negative Prompt",
name: "negativePrompt",
placeholder: "Enter a negative prompt",
onChange: handleNegativePromptChange,
@@ -377,43 +410,36 @@ function SdInferencePanel({
{
label: "All LoRA Weights",
tabKey: "allLoraWeights",
weightTypeFilter: "lora",
weightTypeFilter: "rvc_v2",
searcher: true,
type: "weights",
},
{
label: "Bookmarked",
tabKey: "bookmarkedLoraWeights",
weightTypeFilter: "lora",
weightTypeFilter: "rvc_v2",
searcher: false,
type: "weights",
},
]}
/>
{/* Checkpoint Use weight token */}
{/* <TempSelect
{...{
label: "Checkpoint",
name: "checkPoint",
onChange,
options: tempDeleteMeOpts,
value: checkPoint,
}}
/> */}
<PremiumLock
sessionSubscriptionsWrapper={sessionSubscriptionsWrapper}
requiredPlan="pro"
>
<SegmentButtons
{...{
label: "Number of Generations",
name: "batchCount",
onChange,
options: batchCountOpts,
value: batchCount,
}}
/>
</PremiumLock>
<div>
<Label label="Number of Generations" />
<PremiumLock
sessionSubscriptionsWrapper={sessionSubscriptionsWrapper}
requiredPlan="pro"
>
<SegmentButtons
{...{
name: "batchCount",
onChange,
options: batchCountOpts,
value: batchCount,
}}
/>
</PremiumLock>
</div>
</div>
</Accordion.Item>
</Accordion>
@@ -429,7 +455,7 @@ function SdInferencePanel({
<Button
{...{
label: "Generate Image",
disabled: prompt === "",
disabled: prompt === "" || weightToken === undefined,
onClick: handleEnqueueImageGen,
isLoading: isEnqueuing,
}}