SD weight upload page

This commit is contained in:
BFlat committed 2023-12-30 19:39:02 +07:00
1 parent 67a18a72a6
commit 11d19cf049
8 files changed
+349 -97

No files matched your search

@@ -55,7 +55,7 @@ export default function ImageCard({
let coverImage = "/images/avatars/default-pfp.png";
if (type === "media") {
coverImage = bucketConfig.getCdnUrl(data.public_bucket_path, 400, 100);
coverImage = bucketConfig.getCdnUrl(data.public_bucket_path, 600, 100);
} else if (type === "weights") {
if (data.maybe_cover_image_public_bucket_path) {
coverImage = bucketConfig.getCdnUrl(
@@ -6,17 +6,23 @@ interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
icon?: IconDefinition;
label?: string;
textArea?: boolean;
required?: boolean;
}
export default function TempInput({
label,
icon,
textArea,
required,
...rest
}: InputProps) {
return (
<>
{label && <label className="sub-title">{label}</label>}
{label && (
<label className={`sub-title ${required ? "required" : ""}`}>
{label}
</label>
)}
<div className={`form-group ${icon ? "input-icon" : ""}`}>
{icon && (
<FontAwesomeIcon icon={icon} className="form-control-feedback" />
@@ -16,15 +16,33 @@ export interface SelectProps extends ReactSelectProps {
small?: boolean;
options: Option[];
onChange?: (value: any) => void;
required?: boolean;
}
export default function Select({ label, icon, name, onChange: inChange = () => {}, options = [], rounded, small, value, ...rest }: SelectProps) {
const valueLabel = options.find((option: any) => option.value === value)?.label || "";
const onChange = ({ value }: any) => inChange({ target: { value, name, type: "select" } });
const className = `form-group${ icon ? " input-icon" : "" }${ small ? " select-small" : "" }`;
export default function Select({
label,
icon,
name,
onChange: inChange = () => {},
options = [],
rounded,
small,
value,
required,
...rest
}: SelectProps) {
const valueLabel =
options.find((option: any) => option.value === value)?.label || "";
const onChange = ({ value }: any) =>
inChange({ target: { value, name, type: "select" } });
const className = `form-group${icon ? " input-icon" : ""}${
small ? " select-small" : ""
}`;
const classNames = {
control: ({ isFocused }: { isFocused: boolean }) =>
`select${icon ? " with-icon" : ""}${rounded ? " rounded-full" : ""}${ isFocused ? " focused" : "" }`,
`select${icon ? " with-icon" : ""}${rounded ? " rounded-full" : ""}${
isFocused ? " focused" : ""
}`,
option: () => "select-option",
input: () => "select-input",
placeholder: () => "select-placeholder",
@@ -33,23 +51,31 @@ export default function Select({ label, icon, name, onChange: inChange = () => {
indicatorSeparator: () => "select-separator",
};
return <>
{ label && <label className="sub-title">{label}</label> }
<div {...{ className }}>
{ icon && <FontAwesomeIcon icon={icon} className="form-control-feedback" /> }
<div className="w-100">
<ReactSelect
{...{
classNamePrefix: "select",
classNames,
name,
onChange,
options,
value: { label: valueLabel, value },
...rest,
}}
/>
return (
<>
{label && (
<label className={`sub-title ${required ? "required" : ""}`}>
{label}
</label>
)}
<div {...{ className }}>
{icon && (
<FontAwesomeIcon icon={icon} className="form-control-feedback" />
)}
<div className="w-100">
<ReactSelect
{...{
classNamePrefix: "select",
classNames,
name,
onChange,
options,
value: { label: valueLabel, value },
...rest,
}}
/>
</div>
</div>
</div>
</>;
</>
);
}
@@ -4,13 +4,25 @@ interface TextAreaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
label?: string;
textArea?: boolean;
required?: boolean;
}
export default function TempTextArea({ label, textArea, ...rest }: TextAreaProps) {
return <>
{ label && <label className="sub-title">{ label }</label>}
<div className="form-group">
<textarea className="form-control" { ...rest } />
</div>
</>;
export default function TempTextArea({
label,
textArea,
required,
...rest
}: TextAreaProps) {
return (
<>
{label && (
<label className={`sub-title ${required ? "required" : ""}`}>
{label}
</label>
)}
<div className="form-group">
<textarea className="form-control" {...rest} />
</div>
</>
);
}
@@ -105,6 +105,13 @@ styles with important tags:
box-shadow: none !important;
}
label {
&.required:after {
content: " *";
color: $primary;
}
}
thead,
tbody {
border-color: rgba($white, 0.2) !important;
@@ -104,6 +104,7 @@ import ExplorePage from "./pages/explore/ExplorePage";
import SearchPage from "./search/SearchPage";
import { SearchProvider } from "context/SearchContext";
import WeightEditPage from "./pages/weight/WeightEditPage";
import UploadSdWeightPage from "./pages/upload/UploadSdWeightPage";
interface Props {
sessionWrapper: SessionWrapper;
@@ -353,6 +354,12 @@ class PageContainer extends React.Component<
/>
</Route>
<Route path="/upload/sd">
<UploadSdWeightPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/media/:token">
<MediaPage sessionWrapper={this.props.sessionWrapper} />
</Route>
@@ -0,0 +1,137 @@
import React, { useState } from "react";
import {
faCircleExclamation,
faEye,
faWaveform,
} from "@fortawesome/pro-solid-svg-icons";
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
import PageHeader from "components/layout/PageHeader";
import {
Button,
Container,
Panel,
Skeleton,
SplitPanel,
TempInput,
TempSelect,
TempTextArea,
} from "components/common";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
interface UploadSdWeightPageProps {
sessionWrapper: SessionWrapper;
}
export default function UploadSdWeightPage({
sessionWrapper,
}: UploadSdWeightPageProps) {
const [isLoading] = useState(false);
usePrefixedDocumentTitle("Edit Voice");
const visibilityOptions = [
{ label: "Public", value: "public" },
{ label: "Private", value: "private" },
];
if (!sessionWrapper.isLoggedIn() && !isLoading) {
return (
<Container type="panel">
<PageHeader
titleIcon={faCircleExclamation}
title="Access Denied"
subText="This weight does not exist or is not owned by you."
panel={true}
extension={
<div className="d-flex">
<Button
label="Back to homepage"
to={`/weight/{}`}
className="d-flex"
/>
</div>
}
/>
</Container>
);
}
return (
<Container type="panel">
<PageHeader
title="Upload Stable Diffusion Weight"
titleIcon={faWaveform}
subText="Upload a Stable Diffusion Image model weight. Once your weight is successfully uploaded, you'll be able to start using it and sharing it with others."
panel={false}
/>
<>
{isLoading ? (
<Panel padding={true}>
<div className="d-flex flex-column gap-3">
<Skeleton type="short" />
<Skeleton height="40px" />
<Skeleton type="short" />
<Skeleton height="40px" />
<div className="d-flex justify-content-end mt-3 gap-2">
<Skeleton height="40px" width="120px" />
<Skeleton height="40px" width="120px" />
</div>
</div>
</Panel>
) : (
<SplitPanel dividerFooter={true}>
<SplitPanel.Body padding={true}>
<div className="row gx-4 gy-3">
<div className="col-12 col-lg-4">
<label className="sub-title required">Cover Image</label>
</div>
<div className="col-12 col-lg-8 d-flex flex-column gap-3 order-first order-lg-last">
<div>
<TempInput
{...{
label: "Title",
name: "title",
placeholder: "Title",
required: true,
}}
/>
</div>
<div>
<TempSelect
{...{
icon: faEye,
label: "Visibility",
name: "visibility",
options: visibilityOptions,
placeholder: "Voice name",
}}
/>
</div>
<div>
<TempTextArea
{...{
label: "Description",
name: "descriptionMD",
placeholder: "Description",
}}
/>
</div>
</div>
</div>
</SplitPanel.Body>
<SplitPanel.Footer padding={true}>
<div className="d-flex gap-2 justify-content-end">
<Button
{...{
label: "Upload Weight",
}}
/>
</div>
</SplitPanel.Footer>
</SplitPanel>
)}
</>
</Container>
);
}
@@ -1,11 +1,25 @@
import React, { useState } from "react";
import { useParams } from "react-router-dom";
import { a, useTransition } from "@react-spring/web";
import { faCircleExclamation, faEye, faImage, faWaveform } from "@fortawesome/pro-solid-svg-icons";
import {
faCircleExclamation,
faEye,
faImage,
faWaveform,
} from "@fortawesome/pro-solid-svg-icons";
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
import PageHeader from "components/layout/PageHeader";
import { Button, Container, Panel, Skeleton, SplitPanel,
ImageInput, TempInput, TempSelect, TempTextArea } from "components/common";
import {
Button,
Container,
Panel,
Skeleton,
SplitPanel,
ImageInput,
TempInput,
TempSelect,
TempTextArea,
} from "components/common";
import { WorkIndicator } from "components/svg";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
@@ -18,23 +32,37 @@ interface WeightEditPageProps {
}
// UploadControl will eventually live elsewhere
const UploadControl = ({ onClick, status }:{ onClick: (x: any) => any, status: FetchStatus }) => {
const UploadControl = ({
onClick,
status,
}: {
onClick: (x: any) => any;
status: FetchStatus;
}) => {
const failure = status === FetchStatus.error;
const success = status === FetchStatus.success;
return status === FetchStatus.ready ? <Button {...{
className: "upload-control-btn",
label: "Upload image",
onClick,
variant: "secondary"
}}/> : <div {...{ className: "upload-control-indicator" }}>
<WorkIndicator {...{
failure,
stage: success ? 2 : 1,
success
}}/>
<span>{ success ? "Cover image uploaded" : "Uploading ..." }</span>
</div>;
return status === FetchStatus.ready ? (
<Button
{...{
className: "upload-control-btn",
label: "Upload image",
onClick,
variant: "secondary",
}}
/>
) : (
<div {...{ className: "upload-control-indicator" }}>
<WorkIndicator
{...{
failure,
stage: success ? 2 : 1,
success,
}}
/>
<span>{success ? "Cover image uploaded" : "Uploading ..."}</span>
</div>
);
};
export default function WeightEditPage({
@@ -61,12 +89,14 @@ export default function WeightEditPage({
visibility,
} = useWeightFetch({ token: weight_token });
const src = new BucketConfig().getGcsUrl(weight?.maybe_cover_image_public_bucket_path || "");
const src = new BucketConfig().getGcsUrl(
weight?.maybe_cover_image_public_bucket_path || ""
);
const [editingImg,editingImgSet] = useState(0);
const [editingImg, editingImgSet] = useState(0);
const transitions = useTransition(editingImg, {
config: { tension: 120, friction: 15 },
config: { tension: 120, friction: 15 },
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
@@ -105,7 +135,7 @@ export default function WeightEditPage({
}
return (
<Container type="panel">
<Container type="panel" className="mb-5">
<PageHeader
title="Edit Weight"
titleIcon={faWaveform}
@@ -135,57 +165,84 @@ export default function WeightEditPage({
<SplitPanel.Body padding={true}>
<div {...{ className: "weight-editor w-100 d-flex" }}>
<div {...{ className: "weight-editor-column" }}>
<div {...{ className: "fy-cover-img-input" }}> { weight?.maybe_cover_image_public_bucket_path &&
transitions((style, i) => !i ? <a.div {...{
className: "weight-initial-cover-img",
style: { ...style, backgroundImage: `url(${src})`
} }}>
<div>
<Button {...{
className: "upload-control-btn",
label: "Change cover image",
onClick: () => editingImgSet(1),
variant: "secondary"
}}/>
</div>
</a.div> :
<a.div {...{ style }}>
<ImageInput {...{
...imageProps,
disabled: imgUploadStatus > 1,
placeholderIcon: faImage
}}>
<div {...{ className: "fy-cover-control" }}>
<UploadControl {...{ onClick: uploadCoverImg, status: imgUploadStatus }}/>
</div>
</ImageInput>
</a.div> )
} </div>
<div {...{ className: "fy-cover-img-input" }}>
{" "}
{weight?.maybe_cover_image_public_bucket_path &&
transitions((style, i) =>
!i ? (
<a.div
{...{
className: "weight-initial-cover-img",
style: {
...style,
backgroundImage: `url(${src})`,
},
}}
>
<div>
<Button
{...{
className: "upload-control-btn",
label: "Change cover image",
onClick: () => editingImgSet(1),
variant: "secondary",
}}
/>
</div>
</a.div>
) : (
<a.div {...{ style }}>
<ImageInput
{...{
...imageProps,
disabled: imgUploadStatus > 1,
placeholderIcon: faImage,
}}
>
<div {...{ className: "fy-cover-control" }}>
<UploadControl
{...{
onClick: uploadCoverImg,
status: imgUploadStatus,
}}
/>
</div>
</ImageInput>
</a.div>
)
)}{" "}
</div>
</div>
<div {...{ className: "weight-editor-column" }}>
<TempInput {...{
<TempInput
{...{
label: "Title",
name: "title",
onChange,
placeholder: "Title",
value: title,
}} />
<TempSelect {...{
icon: faEye,
label: "Visibility",
name: "visibility",
options: visibilityOptions,
onChange,
placeholder: "Voice name",
value: visibility,
}} />
<TempTextArea {...{
label: "Description",
name: "descriptionMD",
onChange,
placeholder: "Description",
value: descriptionMD,
}} />
}}
/>
<TempSelect
{...{
icon: faEye,
label: "Visibility",
name: "visibility",
options: visibilityOptions,
onChange,
placeholder: "Voice name",
value: visibility,
}}
/>
<TempTextArea
{...{
label: "Description",
name: "descriptionMD",
onChange,
placeholder: "Description",
value: descriptionMD,
}}
/>
</div>
</div>
</SplitPanel.Body>