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

This commit is contained in:
Victor committed 2024-02-09 22:24:51 -08:00
commit c9fc8f0a03
11 files changed
+324 -148

No files matched your search

@@ -23,6 +23,7 @@ interface AudioCardProps {
type: "media" | "weights";
inSelectModal?: boolean;
onResultSelect?: (data: { token: string; title: string }) => void;
onResultBookmarkSelect?: (data: { token: string; title: string }) => void;
// onClick?: (e:any) => any;
}
@@ -37,16 +38,24 @@ export default function AudioCard({
inSelectModal = false,
// onClick: inClick,
onResultSelect,
onResultBookmarkSelect,
}: AudioCardProps) {
const linkUrl = getCardUrl(data, source, type);
const history = useHistory();
const handleSelectModalResultSelect = () => {
if (inSelectModal && onResultSelect) {
onResultSelect({
token: data.weight_token,
title: data.title,
});
if (inSelectModal) {
onResultSelect &&
onResultSelect({
token: data.weight_token,
title: data.title,
});
onResultBookmarkSelect &&
onResultBookmarkSelect({
token: data.details.entity_token,
title: data.details.maybe_weight_data.title,
});
}
};
@@ -90,7 +99,9 @@ export default function AudioCard({
<div className="mb-3">
<div className="d-flex align-items-center">
<div className="d-flex flex-grow-1 align-items-center gap-2">
<Badge {...{ className: "fy-entity-type-audio", label: "Audio" }} />
<Badge
{...{ className: "fy-entity-type-audio", label: "Audio" }}
/>
</div>
</div>
@@ -102,14 +113,16 @@ export default function AudioCard({
<p className="fs-7 opacity-75">{timeAgo}</p>
</div>
<AudioPlayer src={data.public_bucket_path} id={data.token} />
<CardFooter {...{
creator: data?.maybe_creator,
entityToken: data.token,
entityType: "media_file",
makeBookmarksProps: bookmarks.makeProps,
makeRatingsProps: ratings.makeProps,
showCreator
}}/>
<CardFooter
{...{
creator: data?.maybe_creator,
entityToken: data.token,
entityType: "media_file",
makeBookmarksProps: bookmarks.makeProps,
makeRatingsProps: ratings.makeProps,
showCreator,
}}
/>
</>
)}
@@ -163,14 +176,16 @@ export default function AudioCard({
</div>
</div>
</div>
<CardFooter {...{
creator: data?.maybe_creator,
entityToken: data.weight_token,
entityType: "model_weight",
makeBookmarksProps: bookmarks.makeProps,
makeRatingsProps: ratings.makeProps,
showCreator
}}/>
<CardFooter
{...{
creator: data?.maybe_creator,
entityToken: data.weight_token,
entityType: "model_weight",
makeBookmarksProps: bookmarks.makeProps,
makeRatingsProps: ratings.makeProps,
showCreator,
}}
/>
</>
)}
</Card>
@@ -20,6 +20,7 @@ interface ImageCardProps {
type: "media" | "weights";
inSelectModal?: boolean;
onResultSelect?: (data: { token: string; title: string }) => void;
onResultBookmarkSelect?: (data: { token: string; title: string }) => void;
}
export default function ImageCard({
@@ -31,6 +32,7 @@ export default function ImageCard({
type,
inSelectModal = false,
onResultSelect,
onResultBookmarkSelect,
}: ImageCardProps) {
const history = useHistory();
// const { setToken, setWeightTitle } = useToken();
@@ -44,6 +46,12 @@ export default function ImageCard({
token: data.weight_token,
title: data.title,
});
onResultBookmarkSelect &&
onResultBookmarkSelect({
token: data.details.entity_token,
title: data.details.maybe_weight_data.title,
});
}
};
@@ -68,6 +68,6 @@
.modal-content {
// width: 85vw;
min-height: 70vh;
padding: 1rem;
padding: 1.5rem;
}
}
@@ -127,6 +127,8 @@ export default function Searcher({
foundWeights.map((data: any, key: number) => {
let props = {
data,
ratings,
bookmarks,
type: "weights",
inSelectModal: true,
onResultSelect,
@@ -11,13 +11,14 @@ import SelectWeightsList from "./SelectWeightsList";
export type SelectModalData = {
token: string;
title: string;
}
};
interface TabConfig {
label: string;
tabKey: string;
type: "media" | "weights";
typeFilter?: string;
searcher?: boolean;
onlyBookmarked?: boolean;
}
interface SelectModalProps {
label?: string;
@@ -37,42 +38,46 @@ const SelectModal = memo(
value,
required,
}: SelectModalProps) => {
const emptyValue = {token:"", title:""};
const [{isModalOpen, selectedValue, valueType, activeTab}, setState] = useState({
isModalOpen:false,
selectedValue: value ? value : emptyValue,
activeTab: tabs[0].tabKey,
valueType: tabs[0].typeFilter || "all"
})
const emptyValue = { token: "", title: "" };
const [{ isModalOpen, selectedValue, valueType, activeTab }, setState] =
useState({
isModalOpen: false,
selectedValue: value ? value : emptyValue,
activeTab: tabs[0].tabKey,
valueType: tabs[0].typeFilter || "all",
});
console.log("🔫", tabs, activeTab, valueType);
// Update mediaType when activeTab changes
useEffect(() => {
const currentTab = tabs.find(tab => tab.tabKey === activeTab);
setState((curr)=>({...curr, valueType: currentTab?.typeFilter || "all"}))
setState(curr => ({
...curr,
valueType: currentTab?.typeFilter || "all",
}));
}, [activeTab, tabs]);
const openModal = () => {
setState((curr)=>({...curr, isModalOpen: true}));
setState(curr => ({ ...curr, isModalOpen: true }));
};
const closeModal = () => {
setState((curr)=>({...curr, isModalOpen: false}));
setState(curr => ({ ...curr, isModalOpen: false }));
};
const handleRemove = () => {
setState((curr)=>({...curr, selectedValue: emptyValue}));
setState(curr => ({ ...curr, selectedValue: emptyValue }));
if (onSelect) onSelect(emptyValue);
};
const handleOnSelect = (data:{token:string, title:string}) => {
setState((curr)=>({
const handleOnSelect = (data: { token: string; title: string }) => {
setState(curr => ({
...curr,
selectedValue: {token: data.token, title: data.title || ""},
isModalOpen: false
selectedValue: { token: data.token, title: data.title || "" },
isModalOpen: false,
}));
if (onSelect) onSelect(data);
}
};
const searchTabs = tabs.map(tab => ({
label: tab.label,
@@ -93,16 +98,27 @@ const SelectModal = memo(
/>
)}
{tab.type === "weights" && (
<SelectWeightsList
weightType={valueType}
listKey={tab.tabKey}
onResultSelect={handleOnSelect}
/>
<>
{tab.onlyBookmarked ? (
<SelectWeightsList
weightType={valueType}
listKey={tab.tabKey}
onResultBookmarkSelect={handleOnSelect}
onlyBookmarked={tab.onlyBookmarked}
/>
) : (
<SelectWeightsList
weightType={valueType}
listKey={tab.tabKey}
onResultSelect={handleOnSelect}
/>
)}
</>
)}
</>
),
padding: true,
onClick: () => setState((curr)=>({...curr, activeTab: tab.tabKey})),
onClick: () => setState(curr => ({ ...curr, activeTab: tab.tabKey })),
}));
return (
@@ -114,32 +130,35 @@ const SelectModal = memo(
</label>
)}
<div className="d-flex gap-2 position-relative">
<div
className="position-absolute w-100 h-100"
style={{"cursor": "pointer"}}
onClick={openModal}
/>
<div className="d-flex">
<Input
disabled={true}
wrapperClassName="w-100"
readOnly={true}
placeholder="None selected"
onClick={openModal}
value={selectedValue.title !==""
? selectedValue.title
: selectedValue.token || ""}
value={
selectedValue.title !== ""
? selectedValue.title
: selectedValue.token || ""
}
style={{ cursor: "pointer" }}
wrapperClassName="flex-grow-1"
/>
<Button label={selectedValue.token !== "" ? "Change" : "Select"} onClick={openModal} />
{selectedValue.token && (
<div className="d-flex gap-2">
<Button
square={true}
variant="danger"
icon={faTrash}
onClick={handleRemove}
tooltip="Remove"
label={selectedValue.token !== "" ? "Change" : "Select"}
onClick={openModal}
/>
)}
{selectedValue.token && (
<Button
square={true}
variant="danger"
icon={faTrash}
onClick={handleRemove}
tooltip="Remove"
/>
)}
</div>
</div>
</div>
@@ -159,4 +178,4 @@ const SelectModal = memo(
}
);
export default SelectModal;
export default SelectModal;
@@ -1,32 +1,55 @@
import React, { useRef, useState } from "react";
import MasonryGrid from "../MasonryGrid/MasonryGrid";
import InfiniteScroll from "react-infinite-scroll-component";
import { useLazyLists } from "hooks";
import {
useBookmarks,
useLazyLists,
useListContent,
useRatings,
useSession,
} from "hooks";
import prepFilter from "resources/prepFilter";
import WeightsCards from "../Card/WeightsCards";
import { ListWeights } from "@storyteller/components/src/api/weights/ListWeights";
import { Weight } from "@storyteller/components/src/api/weights/GetWeight";
import { GetBookmarksByUser } from "@storyteller/components/src/api/bookmarks/GetBookmarksByUser";
import Pagination from "../Pagination";
interface SelectWeightsListProps {
weightType: string;
listKey: string;
onResultSelect?: (data:{token:string, title:string}) => void;
onResultSelect?: (data: { token: string; title: string }) => void;
onResultBookmarkSelect?: (data: { token: string; title: string }) => void;
onlyBookmarked?: boolean;
}
export default function SelectWeightsList({
weightType,
listKey,
onResultSelect,
onResultBookmarkSelect,
onlyBookmarked,
}: SelectWeightsListProps) {
const gridContainerRef = useRef<HTMLDivElement | null>(null);
const gridContainerRef2 = useRef<HTMLDivElement | null>(null);
const [list, listSet] = useState<Weight[]>([]);
const [bookmarksList, setBookmarksList] = useState<Weight[]>([]);
const bookmarks = useBookmarks();
const ratings = useRatings();
const { user } = useSession();
const [showBookmarksMasonryGrid, setShowBookmarksMasonryGrid] =
useState(false);
const handlePageClick = (selectedItem: { selected: number }) => {
pageChange(selectedItem.selected);
};
//weights
const weights = useLazyLists({
addQueries: {
page_size: 9,
...prepFilter(weightType, "weight_type"),
},
debug: "Weights List",
fetcher: ListWeights,
list,
listSet,
@@ -34,61 +57,164 @@ export default function SelectWeightsList({
disableUrlQueries: true,
});
//bookmarks
const { isLoading, page, pageChange, pageCount, status } = useListContent({
addQueries: {
page_size: 9,
...prepFilter(weightType, "weight_type"),
},
fetcher: GetBookmarksByUser,
onSuccess: res => {
setShowBookmarksMasonryGrid(true);
},
list: bookmarksList,
debug: "bookmarks tab",
listSet: setBookmarksList,
requestList: true,
urlParam: user.username,
disableUrlQueries: true,
});
const paginationProps = {
onPageChange: handlePageClick,
pageCount,
currentPage: page,
};
return (
<div className="searcher-container in-modal" id={listKey}>
<InfiniteScroll
dataLength={weights.list.length}
next={weights.getMore}
hasMore={!weights.list.length || !!weights.next}
loader={
weights.list.length !== 0 &&
weights.isLoading && (
<div className="mt-4 d-flex justify-content-center">
<div className="spinner-border text-light" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
)
}
endMessage={
<p className="text-center mt-4 opacity-75">No more results.</p>
}
className="overflow-hidden"
scrollableTarget={listKey}
scrollThreshold={0.95}
>
{onlyBookmarked ? (
<>
{weights.list.length === 0 && weights.status === 3 ? (
<div className="text-center opacity-75">No weight created yet.</div>
) : (
<MasonryGrid
gridRef={gridContainerRef}
onLayoutComplete={() => console.log("Layout complete!")}
>
{weights.list.map((data: any, key: number) => {
let props = {
data,
showCreator: true,
type: "weights",
inSelectModal: true,
onResultSelect,
};
return (
<div
{...{
className: "col-12 col-sm-6 col-xl-4 grid-item",
key,
}}
>
<WeightsCards {...{ type: data.weight_category, props }} />
<div className="d-flex flex-wrap gap-3 mb-3">
<Pagination {...paginationProps} />
</div>
<>
{isLoading ? (
<div className="mt-4 d-flex justify-content-center">
<div className="spinner-border text-light" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
) : (
<>
{bookmarksList.length === 0 && status === 3 ? (
<div className="text-center mt-4 opacity-75">
No bookmarked weights yet.
</div>
);
})}
</MasonryGrid>
)}
) : (
<>
{showBookmarksMasonryGrid &&
Array.isArray(bookmarksList) && (
<MasonryGrid
gridRef={gridContainerRef2}
onLayoutComplete={() =>
console.log("Layout complete!")
}
>
{bookmarksList.map((data: any, key: number) => {
let weightProps = {
bookmarks,
data,
ratings,
type: "weights",
inSelectModal: true,
onResultBookmarkSelect,
};
return (
<div
{...{
className:
"col-12 col-sm-6 col-xl-4 grid-item",
key,
}}
>
<WeightsCards
{...{
type: data.details.maybe_weight_data
.weight_category,
props: weightProps,
}}
/>
{/* <MediaCards
{...{ type: data.media_type, props: mediaProps }}
/> */}
</div>
);
})}
</MasonryGrid>
)}
</>
)}
</>
)}
</>
<div className="d-flex justify-content-end mt-4">
<Pagination {...paginationProps} />
</div>
</>
</InfiniteScroll>
) : (
<InfiniteScroll
dataLength={weights.list.length}
next={weights.getMore}
hasMore={!weights.list.length || !!weights.next}
loader={
weights.list.length !== 0 &&
weights.isLoading && (
<div className="mt-4 d-flex justify-content-center">
<div className="spinner-border text-light" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
)
}
endMessage={
<p className="text-center mt-4 opacity-75">No more results.</p>
}
className="overflow-hidden"
scrollableTarget={listKey}
scrollThreshold={0.95}
>
<>
{weights.list.length === 0 && weights.status === 3 ? (
<div className="text-center opacity-75">
No weight created yet.
</div>
) : (
<MasonryGrid
gridRef={gridContainerRef}
onLayoutComplete={() => console.log("Layout complete!")}
>
{weights.list.map((data: any, key: number) => {
let props = {
data,
ratings,
bookmarks,
showCreator: true,
type: "weights",
inSelectModal: true,
onResultSelect,
};
return (
<div
{...{
className: "col-12 col-sm-6 col-xl-4 grid-item",
key,
}}
>
<WeightsCards
{...{ type: data.weight_category, props }}
/>
</div>
);
})}
</MasonryGrid>
)}
</>
</InfiniteScroll>
)}
</div>
);
}
@@ -5,8 +5,8 @@ import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import "./Input.scss";
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
wrapperClassName ?: string;
className?: string;
wrapperClassName?: string;
className?: string;
icon?: IconDefinition;
invalidReason?: string;
label?: string;
@@ -26,13 +26,20 @@ export default function TempInput({
}: InputProps) {
return (
// Changed fragment to div here just so that it can be laid out with bootstrap easily using d-flex, flex-column and responsive gaps which requires grouping.
<div className={`fy-input${wrapperClassName ? " "+ wrapperClassName : ""}`}>
{label && <Label {...{ invalidReason, label, required }}/>}
<div className={`fy-input ${icon ? "input-icon" : ""}`}>
<div
className={`fy-input ${wrapperClassName ? " " + wrapperClassName : ""}`}
>
{label && <Label {...{ invalidReason, label, required }} />}
<div className={`${icon ? "input-icon" : ""}`}>
{icon && (
<FontAwesomeIcon icon={icon} className="form-control-feedback" />
)}
<input {...{ className: `form-control${ className ? " " + className : "" }`, ...rest }} />
<input
{...{
className: `form-control${className ? " " + className : ""}`,
...rest,
}}
/>
</div>
</div>
);
@@ -15,6 +15,7 @@ interface Props {
requestList?: boolean;
urlParam: string;
urlUpdate?: boolean;
disableUrlQueries?: boolean;
}
const n = () => {};
@@ -31,12 +32,15 @@ export default function useListContent({
pagePreset = 0,
requestList = false,
urlParam = "",
urlUpdate = true
urlUpdate = true,
disableUrlQueries = false,
}: Props) {
const { pathname, search: locSearch } = useLocation();
const history = useHistory();
const urlQueries = new URLSearchParams(locSearch);
const [page, pageSet] = useState(parseInt(urlQueries.get("page_index") || "") || pagePreset);
const [page, pageSet] = useState(
parseInt(urlQueries.get("page_index") || "") || pagePreset
);
const [pageCount, pageCountSet] = useState(0);
const [sort, sortSet] = useState(urlQueries.get("sort_ascending") === "true");
const [status, statusSet] = useState(
@@ -78,8 +82,10 @@ export default function useListContent({
if (status === FetchStatus.ready) {
let search = new URLSearchParams(queries).toString();
statusSet(FetchStatus.in_progress);
if (urlUpdate) history.replace({ pathname, search });
fetcher(urlParam,{},queries).then((res: any) => {
!disableUrlQueries &&
urlUpdate &&
history.replace({ pathname, search });
fetcher(urlParam, {}, queries).then((res: any) => {
if (debug)
console.log(`🪲 useListContent success debug at: ${debug}`, res);
statusSet(FetchStatus.success);
@@ -103,7 +109,8 @@ export default function useListContent({
sort,
status,
urlParam,
urlUpdate
urlUpdate,
disableUrlQueries,
]);
return {
@@ -6,7 +6,7 @@ import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/Infer
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";
// import { useInferenceJobs } from "hooks";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
@@ -24,11 +24,6 @@ export default function TextToImagePage({
sessionSubscriptionsWrapper,
enqueueInferenceJob,
}: TextToImagePageProps) {
const { inferenceJobs } = useInferenceJobs(
FrontendInferenceJobType.ImageGeneration
);
const hasImageGenJobs = inferenceJobs && inferenceJobs.length > 0;
if (!sessionWrapper.canAccessStudio()) {
return <StudioNotAvailable />;
}
@@ -43,17 +38,15 @@ export default function TextToImagePage({
yOffset="68%"
/>
{hasImageGenJobs && (
<div className="mb-4">
<TextToImageJobsList />
</div>
)}
<SdInferencePanel
sessionSubscriptionsWrapper={sessionSubscriptionsWrapper}
enqueueInferenceJob={enqueueInferenceJob}
isStandalone={true}
/>
<div className="mt-4">
<TextToImageJobsList />
</div>
</Container>
);
}
@@ -1,6 +1,4 @@
import React from "react";
import { Analytics } from "common/Analytics";
import InferenceJobsList from "components/layout/InferenceJobsList";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
@@ -18,7 +16,6 @@ export default function TextToImageJobsList() {
<InferenceJobsList
{...{
failures,
onSelect: () => Analytics.voiceConversionClickDownload(),
jobType: FrontendInferenceJobType.ImageGeneration,
}}
/>
@@ -301,7 +301,7 @@ function SdInferencePanel({
<div>
<Button
icon={faUpload}
label="Upload a Weight"
label="Upload Weight"
variant="secondary"
small={true}
to="/upload/sd"
@@ -332,6 +332,7 @@ function SdInferencePanel({
typeFilter: "sd_1.5",
searcher: false,
type: "weights",
onlyBookmarked: true,
},
]}
/>
@@ -438,6 +439,7 @@ function SdInferencePanel({
typeFilter: "loRA",
searcher: false,
type: "weights",
onlyBookmarked: true,
},
]}
/>
@@ -471,7 +473,7 @@ function SdInferencePanel({
<Button
{...{
label: "Generate Image",
disabled: prompt === "" || weightToken === undefined,
disabled: prompt === "" || weightToken === "",
onClick: handleEnqueueImageGen,
isLoading: isEnqueuing,
}}