Merge branch 'feature/mvp' into feature/page/video_transfer

This commit is contained in:
wil committed 2024-02-17 16:22:40 -05:00
commit d8a50334fd
35 files changed
+386 -288

No files matched your search

@@ -8,6 +8,7 @@ export interface UploadLoraRequest {
maybe_name: String,
maybe_description: String,
visibility: String,
maybe_cover_image_media_file_token?: String,
}
export interface UploadLoraResponse {
@@ -8,6 +8,7 @@ export interface UploadModelRequest {
maybe_name: String,
maybe_description: String,
visibility: String,
maybe_cover_image_media_file_token?: String,
}
export interface UploadModelResponse {
@@ -21,6 +21,7 @@ export interface MediaFile {
maybe_weight_creator: UserDetailsLight;
maybe_cover_image_public_bucket_path: string;
};
maybe_text_transcript?: string;
}
export enum MediaFileLookupError {
+15 -1
View File
@@ -300,6 +300,8 @@ class App extends React.Component<Props, State> {
await this.querySessionSubscriptions();
setInterval(async () => {
// See warnings in the following methods when adding new methods
// that affect global "state"
await this.querySession();
await this.querySessionSubscriptions();
}, 60000);
@@ -310,6 +312,10 @@ class App extends React.Component<Props, State> {
}
querySession = async () => {
// WARNING: Making setState calls in this scope without checking existing
// state can cause the whole site to refresh/worsen UX. Double check if
// state needs to be set here, or if instead can be refreshed locally on
// the page where the new "state" needed
const sessionWrapper = await SessionWrapper.lookupSession();
const username = sessionWrapper.getDisplayName();
const cookies = new Cookies();
@@ -337,7 +343,15 @@ class App extends React.Component<Props, State> {
};
querySessionSubscriptions = async () => {
this.setState({ sessionFetched: true });
// WARNING: Making setState calls in this scope without checking existing
// state can cause the whole site to refresh/worsen UX. Double check if
// state needs to be set here, or if instead can be refreshed locally on
// the page where the new "state" needed
if (this.state.sessionFetched === false){
this.setState({ sessionFetched: true });
}
const cookies = new Cookies();
const sessionSubscriptionsWrapper =
@@ -106,17 +106,22 @@ export default function AudioCard({
</div>
<h6 className="fw-semibold text-white mb-1 mt-3">
{data.origin.maybe_model
{data.origin?.maybe_model
? data.origin.maybe_model.title
: "Media Audio"}
</h6>
<p className="fs-7 opacity-75">{timeAgo}</p>
{data.maybe_text_transcript && (
<p className="fs-7 mt-2 two-line-ellipsis">
{data.maybe_text_transcript}
</p>
)}
</div>
<AudioPlayer src={data.public_bucket_path} id={data.token} />
<AudioPlayer src={data.details?.maybe_media_file_data?.public_bucket_path || data.public_bucket_path} id={data.token} />
<CardFooter
{...{
creator: data?.maybe_creator,
entityToken: data.token,
creator: data?.maybe_creator || data.details?.maybe_media_file_data?.maybe_creator,
entityToken: data.details?.entity_token || data.token,
entityType: "media_file",
makeBookmarksProps: bookmarks?.makeProps,
makeRatingsProps: ratings?.makeProps,
@@ -178,8 +183,7 @@ export default function AudioCard({
</div>
<CardFooter
{...{
creator:
data?.creator || data.details.maybe_weight_data?.maybe_creator,
creator: data?.creator || data.details.maybe_weight_data?.maybe_creator,
entityToken: data.weight_token || data.details?.entity_token,
entityType: "model_weight",
makeBookmarksProps: bookmarks?.makeProps,
@@ -55,8 +55,8 @@ export default function BVHCard({
<p className="fs-7 opacity-75 mb-0">{timeAgo}</p>
</div>
<CardFooter {...{
creator: data?.maybe_creator,
entityToken: data.token,
creator: data?.maybe_creator || data.details?.maybe_media_file_data?.maybe_creator,
entityToken: data.details?.entity_token || data.token,
entityType: "media_file",
makeBookmarksProps: bookmarks?.makeProps,
makeRatingsProps: ratings?.makeProps,
@@ -0,0 +1,14 @@
import React from "react";
import MediaCards from "components/common/Card/MediaCards";
import WeightsCards from "components/common/Card/WeightsCards";
interface Props {
entityType: string;
props: any;
type: string;
}
export default function BookmarksCards({ entityType, props, type }: Props) {
return entityType === "media_file" ?
<MediaCards { ...{ props, type }} /> : <WeightsCards { ...{ props, type }} />;
}
@@ -54,8 +54,8 @@ export default function FBXCard({
<p className="fs-7 opacity-75 mb-0">{timeAgo}</p>
</div>
<CardFooter {...{
creator: data?.maybe_creator,
entityToken: data.token,
creator: data?.maybe_creator || data.details?.maybe_media_file_data?.maybe_creator,
entityToken: data.details?.entity_token || data.token,
entityType: "media_file",
makeBookmarksProps: bookmarks?.makeProps,
makeRatingsProps: ratings?.makeProps,
@@ -54,8 +54,8 @@ export default function GLBCard({
<p className="fs-7 opacity-75 mb-0">{timeAgo}</p>
</div>
<CardFooter {...{
creator: data?.maybe_creator,
entityToken: data.token,
creator: data?.maybe_creator || data.details?.maybe_media_file_data?.maybe_creator,
entityToken: data.details?.entity_token || data.token,
entityType: "media_file",
makeBookmarksProps: bookmarks?.makeProps,
makeRatingsProps: ratings?.makeProps,
@@ -54,8 +54,8 @@ export default function GLTFCard({
<p className="fs-7 opacity-75 mb-0">{timeAgo}</p>
</div>
<CardFooter {...{
creator: data?.maybe_creator,
entityToken: data.token,
creator: data?.maybe_creator || data.details?.maybe_media_file_data?.maybe_creator,
entityToken: data.details?.entity_token || data.token,
entityType: "media_file",
makeBookmarksProps: bookmarks?.makeProps,
makeRatingsProps: ratings?.makeProps,
@@ -66,7 +66,7 @@ export default function ImageCard({
let coverImage = undefined;
if (type === "media") {
coverImage = bucketConfig.getCdnUrl(data.public_bucket_path, 600, 100);
coverImage = bucketConfig.getCdnUrl(data.details?.maybe_media_file_data?.public_bucket_path || data.public_bucket_path, 600, 100);
} else if (type === "weights") {
coverImage = `/images/default-covers/${
data?.cover_image?.default_cover.image_index || 0
@@ -121,8 +121,8 @@ export default function ImageCard({
</div>
<CardFooter
{...{
creator: data?.maybe_creator,
entityToken: data.token,
creator: data?.maybe_creator || data.details?.maybe_media_file_data?.maybe_creator,
entityToken: data.details?.entity_token || data.token,
entityType: "media_file",
makeBookmarksProps: bookmarks?.makeProps,
makeRatingsProps: ratings?.makeProps,
@@ -17,6 +17,6 @@ export default function WeightsCards({ props, type }: Props) {
case WeightCategory.SD:
return <ImageCard {...props} />;
default:
return <div>Unsupported media type</div>;
return <div>Unsupported weight type</div>;
}
}
@@ -1,5 +1,7 @@
const getCardUrl = (data: any, source: string, type: string) => {
let prefix = type === "media" ? `/media/${data.token}` : `/weight/${data.weight_token || data.details?.entity_token}`;
let isBookmark = data.token && data.token.startsWith("ub_");
let bookmarkedToken = data.details?.entity_token;
let prefix = type === "media" ? `/media/${isBookmark ? bookmarkedToken : data.token}` : `/weight/${isBookmark ? bookmarkedToken : data.weight_token }`;
let suffix = source ? "?source=" + source : "";
return prefix + suffix;
}
@@ -7,6 +7,7 @@ import Button from "../Button";
import { faTrash } from "@fortawesome/free-solid-svg-icons";
import SelectMediaList from "./SelectMediaList";
import SelectWeightsList from "./SelectWeightsList";
import { useSession } from "hooks";
export type SelectModalData = {
token: string;
@@ -39,6 +40,7 @@ const SelectModal = memo(
required,
}: SelectModalProps) => {
const emptyValue = { token: "", title: "" };
const { user } = useSession();
const [{ isModalOpen, selectedValue, valueType, activeTab }, setState] =
useState({
isModalOpen: false,
@@ -98,12 +100,20 @@ const SelectModal = memo(
{tab.type === "weights" && (
<>
{tab.onlyBookmarked ? (
<SelectWeightsList
weightType={valueType}
listKey={tab.tabKey}
onResultBookmarkSelect={handleOnSelect}
onlyBookmarked={tab.onlyBookmarked}
/>
<>
{user ? (
<SelectWeightsList
weightType={valueType}
listKey={tab.tabKey}
onResultBookmarkSelect={handleOnSelect}
onlyBookmarked={tab.onlyBookmarked}
/>
) : (
<div className="text-center py-3">
<p>Please login to view your bookmarks.</p>
</div>
)}
</>
) : (
<SelectWeightsList
weightType={valueType}
@@ -4,8 +4,10 @@ import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import ReactSelect, { Props as ReactSelectProps } from "react-select";
import "./TempSelect.scss";
type OptionValue = string | number | boolean;
export interface Option {
value: string | number | boolean;
value: OptionValue;
label: string;
}
@@ -33,8 +35,14 @@ export default function Select({
}: SelectProps) {
const valueLabel =
options.find((option: any) => option.value === value)?.label || "";
const onChange = ({ value }: any) =>
inChange({ target: { value, name, type: "select" } });
const onChange = (option: any, x: any) => {
// console.log("🧪",option, x); // WIP -V
if (Array.isArray(option)) {
} else {
inChange({ target: {value: option.value, name, type: "select" } });
}
}
const className = `${icon ? " input-icon" : ""}${
small ? " select-small" : ""
}`;
@@ -21,6 +21,7 @@ import MocapInput from "./MocapInput";
import Modal from "./Modal";
import NumberSlider from "./NumberSlider";
import NumberSliderV2 from "./NumberSliderV2";
import Pagination from "./Pagination";
import Panel from "./Panel";
import Radio from "./Radio";
import SegmentButtons from "./SegmentButtons";
@@ -66,6 +67,7 @@ export {
Modal,
NumberSlider,
NumberSliderV2,
Pagination,
Panel,
Radio,
SegmentButtons,
@@ -3,45 +3,27 @@ import { useLocation } from "react-router-dom";
import { a, useTransition } from "@react-spring/web";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
import Iframe from "react-iframe";
import { MediaBrowser } from "components/modals";
import { FileWrapper, Label, Spinner } from "components/common";
import { EntityType, MediaFilterProp, WeightFilterProp } from "components/entities/EntityTypes";
import { Label, Spinner } from "components/common";
import { AcceptTypes, EntityInputMode, EntityModeProp } from "components/entities/EntityTypes";
import { useFile, useMedia, useModal, useSession } from "hooks";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import { faDiagramSankey, faFile, faFileArrowUp, faGrid, faImage, faPersonWalking, faWaveform } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon as Icon } from "@fortawesome/react-fontawesome";
import { UploadMedia, UploadMediaResponse } from "@storyteller/components/src/api/media_files/UploadMedia";
import { v4 as uuidv4 } from "uuid";
import EntityInputEmpty from "./EntityInputEmpty";
import "./EntityInput.scss";
// enum EntityInputMode {
// Bookmarks = "bookmarks",
// Media = "media",
// Weights = "weights",
// SearchWeights = "searchWeights",
// }
interface Props {
accept?: AcceptTypes | AcceptTypes[],
aspectRatio?: "square" | "landscape" | "portrait",
mediaType?: MediaFilterProp,
label?: string,
onChange?: any,
type: string,
weightType?: WeightFilterProp
type: EntityModeProp,
}
interface SlideProps {
export interface SlideProps {
media?: MediaFile
};
interface EmptySlideProps extends SlideProps {
entityType: EntityType,
filterType: MediaFilterProp | WeightFilterProp,
inputProps?: any,
open: any,
user: any
};
interface AniProps {
animating: boolean,
className: string,
@@ -56,12 +38,12 @@ const MediaBusy = () => {
const MocapInputFull = ({ media }: SlideProps) => {
const bucketConfig = new BucketConfig();
const bvhUrl = media?.public_bucket_path ? bucketConfig.getGcsUrl(media.public_bucket_path) : "";
const mediaUrl = media?.public_bucket_path ? bucketConfig.getGcsUrl(media.public_bucket_path) : "";
return <>
<Iframe
{...{
url: `https://engine.fakeyou.com?mode=viewer&bvh=${bvhUrl}`,
url: `https://engine.fakeyou.com?mode=viewer&${media?.media_type}=${mediaUrl}`,
className: "fy-entity-input-mocap-preview",
}}
/>
@@ -71,42 +53,6 @@ const MocapInputFull = ({ media }: SlideProps) => {
</>;
};
const MediaPickerEmpty = ({ entityType, filterType, open, inputProps, user, ...rest }: EmptySlideProps) => {
const browserClick = () => open({
component: MediaBrowser,
props: { entityType, filterType, username: user?.username || "", ...rest }
});
const mediaIcons = () => {
switch (filterType) {
case "audio": return faWaveform;
case "image": return faImage;
case "bvh": return faPersonWalking;
default: return faFile;
}
}
return <>
<Icon {...{ className: "fy-entity-input-icon", icon: [faFile,mediaIcons(),faDiagramSankey][entityType] }}/>
<div {...{ className: "fy-entity-input-empty-controls" }}>
{ entityType === EntityType.media &&
<FileWrapper {...{ containerClass: "fy-entity-input-row", panelClass: "fy-entity-input-button", noStyle: true, ...inputProps }}>
<>
<Icon {...{ className: "fy-entity-input-label-icon", icon: faFileArrowUp }}/>
<div {...{ className: "fy-entity-input-upload-detail" }}>
Upload, click or drag here
{ filterType !== "all" && <span>{ filterType } files supported</span> }
</div>
</>
</FileWrapper> }
<div {...{ className: "fy-entity-input-row fy-entity-input-button", onClick: browserClick }}>
<Icon {...{ className: "fy-entity-input-label-icon", icon: faGrid }}/>
Choose from your { ["","media","weights"][entityType] }
</div>
</div>
</>;
}
const AniMod = ({ animating, className, isLeaving, render: Render, style, ...rest }: AniProps) => <a.div {...{
className: `fy-slide-frame${ className ? " " + className : "" }`,
style
@@ -114,15 +60,21 @@ const AniMod = ({ animating, className, isLeaving, render: Render, style, ...res
<Render {...{ ...rest, animating }} />
</a.div>;
export default function EntityInput({ aspectRatio = "square", label, onChange, mediaType, weightType, ...rest }: Props) {
const entityType = mediaType ? EntityType.media : weightType ? EntityType.weights : EntityType.unknown;
const filterType = mediaType || weightType || "all";
export default function EntityInput({ accept: inAccept, aspectRatio = "square", label, onChange, type, ...rest }: Props) {
const accept = Array.isArray(inAccept) ? inAccept : [inAccept];
const inputMode = EntityInputMode[type];
const { search } = useLocation();
const presetToken = search ? new URLSearchParams(search).get("preset_token") : "";
const [mediaToken,mediaTokenSet] = useState(presetToken || "");
const { media, mediaSet } = useMedia({ mediaToken });
const { user } = useSession();
const { open } = useModal();
const selectToken = (token: string) => {
mediaTokenSet(token);
onChange({ target: { name: "temp", value: token } });
};
const { clear, inputProps } = useFile({
onChange: (inputFile: any) => {
if (inputFile) UploadMedia({
@@ -133,17 +85,17 @@ export default function EntityInput({ aspectRatio = "square", label, onChange, m
.then((res: UploadMediaResponse) => {
if ("media_file_token" in res) {
clear();
mediaTokenSet(res.media_file_token);
selectToken(res.media_file_token);
}
});
else console.log("🥺 no file");
}
});
const onSelect = (data: MediaFile) => {
mediaSet(data)
console.log("🔅",data);
onChange({ target: { name: "temp", value: data.token } });
}
mediaSet(data);
selectToken(data.token);
};
const busy = false;
const index = busy ? 0 : media ? 1 : 2;
const [animating,animatingSet] = useState(false);
@@ -157,12 +109,10 @@ export default function EntityInput({ aspectRatio = "square", label, onChange, m
onStart: () => animatingSet(true)
});
return <>
<Label {...{ label }}/>
<div {...{ className: `fy-entity-input panel-inner${ aspectRatio ? " fy-entity-input-" + aspectRatio : "" }`, }}>
{
// media ? <MocapInputFull {...{ media }}/> : <MediaPickerEmpty {...{ inputProps, media, onSelect, open, user }}/>
transitions((style: any, i: number, state: any) => {
let isLeaving = state.phase === "leave";
let sharedProps = { animating, isLeaving, media, style };
@@ -170,9 +120,8 @@ export default function EntityInput({ aspectRatio = "square", label, onChange, m
return [
<AniMod {...{ render: MediaBusy, className: "fy-entity-input-busy", ...sharedProps }}/>,
<AniMod {...{ render: MocapInputFull, className: "fy-entity-input-full", ...sharedProps }}/>,
<AniMod {...{ render: MediaPickerEmpty, className: "fy-entity-input-empty", entityType, filterType, inputProps, onSelect, open, user, ...sharedProps, ...rest }}/>
<AniMod {...{ render: EntityInputEmpty, className: "fy-entity-input-empty", accept, inputMode, inputProps, onSelect, open, user, ...sharedProps, ...rest }}/>
][i];
})
}
</div>
@@ -0,0 +1,67 @@
import React from "react";
import { AcceptTypes, EntityInputMode, MediaFilters } from "components/entities/EntityTypes";
import { SlideProps } from "./EntityInput";
import { MediaBrowser } from "components/modals";
import { FileWrapper } from "components/common";
import { FontAwesomeIcon as Icon } from "@fortawesome/react-fontawesome";
import {
faDiagramSankey,
faFile,
faFileArrowUp,
faGrid,
faImage,
faPersonWalking,
faWaveform
} from "@fortawesome/pro-solid-svg-icons";
interface EmptySlideProps extends SlideProps {
accept?: AcceptTypes[],
inputMode: EntityInputMode,
inputProps?: any,
open: any,
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 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;
}
};
const supported = `${ accepted.length ? accepted.join(", ") : accepted[0] } files supported`;
return <>
<Icon {...{ className: "fy-entity-input-icon", icon: [faFile,mediaIcons(),faDiagramSankey,mediaIcons()][inputMode] }}/>
<div {...{ className: "fy-entity-input-empty-controls" }}>
{ isMedia &&
<FileWrapper {...{ containerClass: "fy-entity-input-row", fileTypes: accepted, panelClass: "fy-entity-input-button", noStyle: true, ...inputProps }}>
<>
<Icon {...{ className: "fy-entity-input-label-icon", icon: faFileArrowUp }}/>
<div {...{ className: "fy-entity-input-upload-detail" }}>
Upload, click or drag here
{ accept && <span>{ supported }</span> }
</div>
</>
</FileWrapper> }
<div {...{ className: "fy-entity-input-row fy-entity-input-button", onClick: browserClick }}>
<Icon {...{ className: "fy-entity-input-label-icon", icon: faGrid }}/>
Choose from your { ["bookmarks","media","weights"][inputMode] }
</div>
</div>
</>;
};
@@ -1,9 +1,17 @@
export enum EntityInputMode {
bookmarks,
media,
weights,
searchWeights,
}
export enum MediaFilters {
all,
audio,
image,
video,
bvh,
glb,
gltf,
fbx
}
@@ -21,8 +29,10 @@ export enum WeightsFilters {
vall_e
}
export type EntityModeProp = keyof typeof EntityInputMode;
export type MediaFilterProp = keyof typeof MediaFilters;
export type WeightFilterProp = keyof typeof WeightsFilters;
export type AcceptTypes = MediaFilterProp | WeightFilterProp;
export enum EntityType {
unknown,
@@ -30,12 +40,15 @@ export enum EntityType {
weights
}
export const EntityFilterOptions = ( mode: EntityType, t = (v:string) => v) => {
const filters = [{},MediaFilters,WeightsFilters][mode];
return mode ? Object.values(filters)
export const EntityFilterOptions = ( mode?: EntityInputMode, t = (v:string) => v) => {
const bookmarkFilters = Object.keys({ ...MediaFilters, ...WeightsFilters }).filter(val => isNaN(Number(val))).reduce((obj,current) => ({ ...obj, [current]: current }),{});;
const filters = mode !== undefined ? [bookmarkFilters,MediaFilters,WeightsFilters,WeightsFilters][mode] : EntityInputMode;
return Object.values(filters)
.filter(val => isNaN(Number(val)))
.map((value) => {
if (typeof value === "string") return { value, label: t(value) }
return { label: "all", value: "all" };
}) : [];
});
};
@@ -24,7 +24,7 @@ const MediaCards = ({ props, type }: MediaCardsProps) => {
return <CardWrapper {...{ ...props, card: OverlayCard, preview: ImagePreview }}/>;
case "video":
return <CardWrapper {...{ ...props, card: OverlayCard, preview: VideoPreview }}/>;
case "bvh":
case "bvh": case "glb": case "gltf":
return <CardWrapper {...{ ...props, card: OverlayCard, preview: MocapPreview }}/>;
default:
return <div>Unsupported media type</div>;
@@ -352,30 +352,26 @@ export default function SideNav({
);
}
let maybeImageGeneration = <></>;
if (sessionWrapper.canAccessStudio()) {
maybeImageGeneration = (
<>
<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" />
</>
);
}
let maybeImageGeneration = (
<>
<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" />
</>
);
return (
<>
@@ -1,33 +1,27 @@
import React, { useState } from "react";
import { MediaList } from "components/entities";
import {
EntityType,
EntityFilterOptions,
MediaFilterProp,
WeightFilterProp,
} from "components/entities/EntityTypes";
import { TempSelect } from "components/common";
import { AcceptTypes, EntityInputMode, EntityFilterOptions, } from "components/entities/EntityTypes";
import { Pagination, TempSelect } from "components/common";
import AudioPlayerProvider from "components/common/AudioPlayer/AudioPlayerContext";
import SkeletonCard from "components/common/Card/SkeletonCard";
import Pagination from "components/common/Pagination";
import { GetBookmarksByUser } from "@storyteller/components/src/api/bookmarks/GetBookmarksByUser";
import { GetMediaByUser } from "@storyteller/components/src/api/media_files/GetMediaByUser";
import { GetWeightsByUser } from "@storyteller/components/src/api/weights/GetWeightsByUser";
import { SearchWeights } from "@storyteller/components/src/api/weights/SearchWeights";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
import { Weight } from "@storyteller/components/src/api/weights/GetWeight";
import { useListContent, useRatings } from "hooks";
import {
faArrowDownWideShort,
faFilter,
} from "@fortawesome/pro-solid-svg-icons";
import { useListContent,
// useRatings
} from "hooks";
import { faArrowDownWideShort, faFilter } from "@fortawesome/pro-solid-svg-icons";
import prepFilter from "resources/prepFilter";
import ModalHeader from "../ModalHeader";
import "./MediaBrowser.scss";
interface Props {
entityType: EntityType;
filterType?: MediaFilterProp | WeightFilterProp;
accept?: AcceptTypes[],
handleClose: any;
inputMode: EntityInputMode;
mediaToken: string;
onSelect?: any;
owner?: string;
@@ -36,30 +30,28 @@ interface Props {
}
export default function MediaBrowser({
entityType,
filterType: inputFilter,
accept,
mediaToken,
handleClose = () => {},
inputMode,
onSelect,
owner,
search,
username,
username
}: Props) {
const ratings = useRatings();
// const ratings = useRatings();
const [showMasonryGrid, setShowMasonryGrid] = useState(true);
const [filterType, filterTypeSet] = useState(inputFilter || "all");
const [filterType, filterTypeSet] = useState(accept ? accept[0] : "all");
const [list, listSet] = useState<MediaFile | Weight[]>([]);
const fetcher = search
? SearchWeights
: [() => {}, GetMediaByUser, GetWeightsByUser][entityType] ||
GetMediaByUser;
const fetcher = [GetBookmarksByUser,GetMediaByUser,GetWeightsByUser,SearchWeights][inputMode];
const entities = useListContent({
debug: "media browser",
addQueries: {
...(search ? {} : { page_size: 24 }),
...prepFilter(
filterType,
["", "filter_media_type", "maybe_scoped_weight_type"][entityType]
["maybe_scoped_weight_type", "filter_media_type", "maybe_scoped_weight_type",""][inputMode]
),
},
addSetters: { filterTypeSet },
@@ -69,12 +61,12 @@ export default function MediaBrowser({
onInputChange: () => setShowMasonryGrid(false),
onSuccess: res => {
// bookmarks.gather({ res, key: "token" });
ratings.gather({ res, key: "token" });
// ratings.gather({ res, key: "token" });
setShowMasonryGrid(true);
},
...(search ? { request: { search_term: search } } : {}),
requestList: true,
...(search ? { resultsKey: "weights" } : {}),
// ...(search ? { resultsKey: "weights" } : {}),
urlParam: owner || username,
urlUpdate: false,
});
@@ -95,16 +87,25 @@ export default function MediaBrowser({
// { value: "mostliked", label: "Most Liked" },
];
const title = ["", `${owner ? owner + "'s " : ""}Media`, "Weights"][
entityType
];
const onwerTxt = (entityName: string) => `${ owner ? owner + "'s " : "" }${ entityName }`;
const title = [
onwerTxt("Bookmarks"),
onwerTxt("Media"),
onwerTxt("Weights"),
"Search"
][inputMode];
const onClick = (data: any) => {
console.log("🩵", data);
onSelect(data);
handleClose();
};
const filterOptions = accept ? accept.map((value: string) => ({
value,
label: value
})) : EntityFilterOptions(inputMode);
return (
<>
<ModalHeader {...{ handleClose, title }}>
@@ -117,11 +118,11 @@ export default function MediaBrowser({
value: entities.sort,
}}
/>
{(!inputFilter || inputFilter === "all") && (
{(!accept || (accept && accept.length) ) && (
<TempSelect
{...{
icon: faFilter,
options: EntityFilterOptions(entityType),
options: filterOptions,
name: "filterType",
onChange: entities.onChange,
value: filterType,
@@ -143,7 +144,7 @@ export default function MediaBrowser({
<div {...{ className: "fy-media-browser-list" }}>
<MediaList
{...{
entityType,
entityType: 1,
list: entities.list,
success: entities.status === 3,
onClick,
@@ -3,6 +3,7 @@ import { SessionContext } from "context";
import { Modal } from "components/common";
import AccountModal from "components/layout/AccountModal";
import { ModalView } from "context/SessionContext";
import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
interface Props {
children?: any;
@@ -41,12 +42,11 @@ export default function SessionProvider({
}
};
const userTokenMatch = (otherUserToken: string) =>
!otherUserToken || !user?.user_token
? false
: user.user_token === otherUserToken;
!otherUserToken || !user?.user_token ? false : user.user_token === otherUserToken;
const canEditTtsModel = (userToken: string) =>
user?.canEditOtherUsersTtsModels || userTokenMatch(userToken);
const canBanUsers = () => user?.can_ban_users || false;
const studioAccessCheck = (content: React.ElementType) => !user?.can_access_studio ? <StudioNotAvailable /> : content;
return (
<SessionContext.Provider
@@ -60,6 +60,7 @@ export default function SessionProvider({
querySession,
querySubscriptions,
sessionFetched,
studioAccessCheck,
user,
userTokenMatch,
},
@@ -21,6 +21,7 @@ interface SessionContextType {
querySession?: any;
querySubscriptions?: any;
sessionFetched: boolean;
studioAccessCheck: (x:any) => any,
user?: any;
userTokenMatch: (token: string) => boolean;
}
@@ -31,6 +32,7 @@ export default createContext<SessionContextType>({
check: () => false,
loggedIn: false,
sessionFetched: false,
studioAccessCheck: () => null,
modal: {
close: () => {},
open: () => {},
@@ -53,6 +53,7 @@ export default function useListContent({
const fetchError = status === FetchStatus.error;
const pageChange = (page: number) => {
onInputChange();
pageSet(page);
statusSet(FetchStatus.ready);
};
@@ -29,9 +29,9 @@ export default function useLoraUpload() {
const upload = () => {
writeStatusSet(FetchStatus.in_progress);
UploadLora("", {
//...(coverImg.token
// ? { cover_image_media_file_token: coverImg.token }
// : {}),
...(coverImg.token
? { maybe_cover_image_media_file_token: coverImg.token }
: {}),
maybe_name: title,
maybe_description: descriptionMD,
uuid_idempotency_token: uuidv4(),
@@ -1,7 +1,8 @@
import { useState } from "react";
import { UploadModel } from "@storyteller/components/src/api/image_generation/UploadModel";
import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes";
import { useCoverImgUpload } from "hooks";
import { useCoverImgUpload, useInferenceJobs } from "hooks";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import { v4 as uuidv4 } from "uuid";
// this hook is mostly for organizational purposes while I work -V
@@ -13,6 +14,7 @@ export default function useSdUpload() {
const [descriptionMD, descriptionMDSet] = useState("");
const [writeStatus, writeStatusSet] = useState(FetchStatus.paused);
const coverImg = useCoverImgUpload();
const { enqueue } = useInferenceJobs(FrontendInferenceJobType.ImageGeneration, true);
const onChange = ({ target }: { target: { name: string; value: any } }) => {
const todo: { [key: string]: (x: any) => void } = {
@@ -28,7 +30,7 @@ export default function useSdUpload() {
writeStatusSet(FetchStatus.in_progress);
UploadModel("", {
...(coverImg.token
? { cover_image_media_file_token: coverImg.token }
? { maybe_cover_image_media_file_token: coverImg.token }
: {}),
maybe_description: descriptionMD,
uuid_idempotency_token: uuidv4(),
@@ -39,8 +41,7 @@ export default function useSdUpload() {
})
.then((res: any) => {
writeStatusSet(FetchStatus.success);
console.log("🌠", res);
// history.replace(`/weight/${token}`);
enqueue(res.inference_job_token);
})
.catch(err => {
writeStatusSet(FetchStatus.error);
@@ -60,11 +60,11 @@ export default function EngineCompositor({ value, sessionWrapper }: Props) {
</header>
<EntityInput
{...{
accept: ["bvh","glb","gltf"],
aspectRatio: "landscape",
label: "Choose 3D data",
onChange,
type: "media",
mediaType: "bvh",
}}
/>
</div>
@@ -1,7 +1,7 @@
import React, { useState } from "react";
import { EntityInput } from "components/entities";
import { EntityFilterOptions, EntityType, MediaFilterProp, WeightFilterProp } from "components/entities/EntityTypes";
import { useModal } from "hooks";
import { EntityFilterOptions, EntityInputMode, EntityModeProp } from "components/entities/EntityTypes";
import { useModal, useSession } from "hooks";
import { InferenceJobsModal } from "components/modals";
import { Button, SegmentButtons, TempInput, TempSelect } from "components/common";
@@ -9,42 +9,69 @@ interface Props {
value?: any;
}
interface Yank {
[key: string]: string
}
export default function DevMediaInput({ value }: Props) {
const { studioAccessCheck } = useSession();
const [mediaToken,mediaTokenSet] = useState();
const [mediaType,mediaTypeSet] = useState<MediaFilterProp>("all");
const [weightType,weightTypeSet] = useState<WeightFilterProp>("all");
const [entityType,entityTypeSet] = useState(EntityType.media);
const [mode, modeSet] = useState<EntityModeProp>("media");
const [filters,filtersSet] = useState<Yank>({
bookmarks: "",
media: "",
weights: "",
searchWeights: "",
});
const [owner,ownerSet] = useState("");
const onChange = ({ target }: any) => mediaTokenSet(target.value);
const { open } = useModal();
const options = [{ label: "Media", value: EntityType.media },{ label: "Weights", value: EntityType.weights }];
const changeFilter = ({ target }: { target: any }) => [mediaTypeSet,mediaTypeSet,weightTypeSet][entityType](target.value);
const inputMode = EntityInputMode[mode];
const options = EntityFilterOptions();
const filterOptions = EntityFilterOptions(inputMode);
// console.log("❌", mode, inputMode, filterOptions);
const changeFilter = ({ target }: { target: any }) => filtersSet({ ...filters, [inputMode]: target.value });
const openModal = () => open({ component: InferenceJobsModal });
return <div {...{ className: "fy-engine-compositor"}}>
return studioAccessCheck(<div {...{ className: "fy-engine-compositor"}}>
<div {...{ className: "panel engine-compositor-container" }}>
<header>
<h2>{ [526,187].map((num = 0) => String.fromCodePoint(128000 + num)) }</h2>
<SegmentButtons {...{ onChange: ({ target }: { target: any }) => entityTypeSet(target.value), options, value: entityType }}/>
<SegmentButtons {...{ onChange: ({ target }: { target: any }) => modeSet(target.value), options, value: mode }}/>
<TempInput {...{ value: owner, onChange: ({ target }: { target: any }) => ownerSet(target.value), placeholder: "User" }}/>
<TempSelect {...{ options: EntityFilterOptions(entityType), value: ["",mediaType,weightType][entityType], onChange: changeFilter }}/>
</header>
<EntityInput {...{
aspectRatio: "landscape",
label: `Choose ${ ["","media file","weight"][entityType] }`,
onChange,
owner,
search: "Dream",
type: "Media",
...([{},{ mediaType },{ weightType }][entityType])
}}/>
<Button {...{ className: "mt-3", label: "Open modal", onClick: openModal, variant: "primary" }}/>
<div>
{ mediaToken }
</div>
{
// <div {...{ className: "d-flex" }}>
// { filterOptions.map((option,i) => {
// return <Button {...{ label: option.label }}/>
// }) }
// </div>
}
<TempSelect {...{
isMulti: true,
options: filterOptions,
value: filters[inputMode],
onChange: changeFilter
}}/>
<EntityInput {...{
accept: ["bvh","glb","gltf"],
aspectRatio: "landscape",
label: "not now",
// label: `Choose ${ ["","media file","weight"][entityType] }`,
onChange,
owner,
search: "Dream",
type: "media"
}}/>
<Button {...{ className: "mt-3", label: "Open modal", onClick: openModal, variant: "primary" }}/>
<div>
{ mediaToken }
</div>
</div>
</div>;
</div>);
};
@@ -1,19 +1,18 @@
import React, { useState } from "react";
import Panel from "components/common/Panel";
import Container from "components/common/Container";
import PageHeader from "components/layout/PageHeader";
import React, { useState } from "react";
import { useSession } from "hooks";
import { Button } from "components/common";
import { faUpload } from "@fortawesome/pro-solid-svg-icons";
import {
UploadMedia,
UploadMediaResponse,
} from "@storyteller/components/src/api/media_files/UploadMedia";
import { UploadMedia, UploadMediaResponse, } from "@storyteller/components/src/api/media_files/UploadMedia";
import { v4 as uuidv4 } from "uuid";
import { Link } from "react-router-dom";
interface DevUploadProps {}
export default function DevUpload(props: DevUploadProps) {
const { studioAccessCheck } = useSession();
const [file, fileSet] = useState<File | null>(null);
const [tokens, tokensSet] = useState<string[]>([]);
@@ -38,7 +37,7 @@ export default function DevUpload(props: DevUploadProps) {
else console.log("🥺 no file");
};
return (
return studioAccessCheck(
<Container type="padded" className="pt-4 pt-lg-5">
<PageHeader
title="Upload Generic File"
@@ -75,19 +75,13 @@ export default function MediaPage() {
<MediaAudioComponent mediaFile={mediaFile} />
{/* Show TTS text input if it is a TTS result */}
{mediaFile.public_bucket_path.includes("tts_inference_output") && (
{mediaFile.maybe_text_transcript && (
<div className="mt-4">
<h5 className="fw-semibold">
<FontAwesomeIcon icon={faSquareQuote} className="me-2" />
Audio Text
Audio Text Transcript
</h5>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Pellentesque elit ullamcorper dignissim cras tincidunt
lobortis. Integer malesuada nunc vel risus commodo viverra
maecenas accumsan lacus.
</p>
<p>{mediaFile.maybe_text_transcript}</p>
</div>
)}
</div>
@@ -401,14 +395,14 @@ export default function MediaPage() {
}}
/>
<BookmarkButton
{...{
large: true,
...bookmarks.makeProps({
entityToken: token,
entityType: "media_file",
}),
}}
/>
{...{
large: true,
...bookmarks.makeProps({
entityToken: token,
entityType: "media_file",
}),
}}
/>
</div>
</div>
</div>
@@ -444,11 +438,11 @@ export default function MediaPage() {
}}
/>
) : null}
{mediaFile?.media_type === MediaFileType.BVH ? (
{mediaFile?.media_type === MediaFileType.BVH || mediaFile?.media_type === MediaFileType.GLTF || mediaFile?.media_type === MediaFileType.GLB ? (
<Button
{...{
icon: faVideoPlus,
label: "Use BVH in Engine Compositor",
label: "Use in Engine Compositor",
to: `/engine-compositor?preset_token=${mediaFile.token}`,
variant: "primary",
className: "flex-grow-1",
@@ -11,7 +11,7 @@ import Pagination from "components/common/Pagination";
import { useBookmarks, useListContent, useRatings } from "hooks";
import { GetBookmarksByUser } from "@storyteller/components/src/api/bookmarks/GetBookmarksByUser";
import WeightsCards from "components/common/Card/WeightsCards";
import BookmarksCards from "components/common/Card/BookmarksCards";
import prepFilter from "resources/prepFilter";
export default function BookmarksTab({ username }: { username: string }) {
@@ -210,23 +210,17 @@ export default function BookmarksTab({ username }: { username: string }) {
onLayoutComplete={() => console.log("Layout complete!")}
>
{dataList.map((data: any, key: number) => {
let weightProps = {
let props = {
bookmarks,
data,
// origin,
ratings,
showCreator: true,
source,
type: "weights",
type: data.details?.entity_type === "media_file" ?
"media" : "weights", // this is gross, but I'm replacing all of this anyway -V
};
// let mediaProps = {
// bookmarks,
// data,
// origin,
// type: "media",
// showCreator: true,
// };
return (
<div
{...{
@@ -234,15 +228,11 @@ export default function BookmarksTab({ username }: { username: string }) {
key,
}}
>
<WeightsCards
{...{
type: data.details.maybe_weight_data?.weight_category,
props: weightProps,
}}
/>
{/* <MediaCards
{...{ type: data.media_type, props: mediaProps }}
/> */}
<BookmarksCards {...{
entityType: data.details?.entity_type,
type: data.details.maybe_weight_data?.weight_category || data.details.maybe_media_file_data?.media_type,
props,
}} />
</div>
);
})}
@@ -9,7 +9,7 @@ import {
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
import { faMessageImage } from "@fortawesome/pro-solid-svg-icons";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
//import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
interface TextToImagePageProps {
inferenceJobs: Array<InferenceJob>;
@@ -27,9 +27,9 @@ export default function TextToImagePage({
enqueueInferenceJob,
inferenceJobs,
}: TextToImagePageProps) {
if (!sessionWrapper.canAccessStudio()) {
return <StudioNotAvailable />;
}
//if (!sessionWrapper.canAccessStudio()) {
// return <StudioNotAvailable />;
//}
return (
<Container type="panel">
@@ -41,7 +41,7 @@ import WeightCoverImage from "components/common/WeightCoverImage";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import SdInferencePanel from "./inference_panels/SdInferencePanel";
import SdCoverImagePanel from "./cover_image_panels/SdCoverImagePanel";
import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
//import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
interface WeightProps {
@@ -110,15 +110,15 @@ export default function WeightPage({
const deleteWeight = () => remove(!!user?.can_ban_users);
//Studio Access feature flag
switch (weight?.weight_type) {
case WeightType.SD_15:
case WeightType.SDXL:
case WeightType.LORA:
if (!sessionWrapper.canAccessStudio()) {
return <StudioNotAvailable />;
}
}
////Studio Access feature flag
//switch (weight?.weight_type) {
// case WeightType.SD_15:
// case WeightType.SDXL:
// case WeightType.LORA:
// if (!sessionWrapper.canAccessStudio()) {
// return <StudioNotAvailable />;
// }
//}
//Image generation panel if it's a lora weight or sd weight
let imageGenPanel = <></>;
@@ -82,7 +82,7 @@ function SdInferencePanel({
const [aspectRatio, setAspectRatio] = useState("square");
const [cfgScale, setCfgScale] = useState(7);
const [samples, setSamples] = useState(8);
const [batchCount, setBatchCount] = useState(1);
const [batchCount, setBatchCount] = useState(3);
const [prompt, setPrompt] = useState("");
const [negativePrompt, setNegativePrompt] = useState("");
const onChange = onChanger({
@@ -104,7 +104,7 @@ function SdInferencePanel({
sampler: "DPM++ 2M Karras",
cfgScale: 7,
samples: 8,
batchCount: 1,
batchCount: 3,
loraToken: null,
};
@@ -377,11 +377,36 @@ function SdInferencePanel({
value: aspectRatio,
}}
/>
<div>
<Label label="Number of Generations" />
<PremiumLock
sessionSubscriptionsWrapper={sessionSubscriptionsWrapper}
requiredPlan="pro"
>
<SegmentButtons
{...{
name: "batchCount",
onChange,
options: batchCountOpts,
value: batchCount,
}}
/>
</PremiumLock>
</div>
</div>
<Accordion>
<Accordion.Item title="Advanced">
<div className="p-3 d-flex flex-column gap-3">
<TempSelect
{...{
label: "Sampler",
name: "sampler",
onChange,
options: samplerOpts,
value: sampler,
}}
/>
<div>
<label className="sub-title">Seed</label>
<div className="d-flex gap-2 align-items-center">
@@ -402,15 +427,6 @@ function SdInferencePanel({
/>
</div>
</div>
<TempSelect
{...{
label: "Sampler",
name: "sampler",
onChange,
options: samplerOpts,
value: sampler,
}}
/>
<NumberSlider
{...{
min: 1,
@@ -458,22 +474,6 @@ function SdInferencePanel({
]}
/>
)}
<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>