revised moderation job control page/TempSelect

This commit is contained in:
Victor committed 2024-02-27 21:59:24 -08:00
1 parent 0c4c90075f
commit 2012e5ccf2
9 files changed
+195 -188

No files matched your search

@@ -0,0 +1,24 @@
{
"WeightTypes": {
"all": "All weights",
"hifigan_tt2": "HiFi-GAN TT2",
"rvc_v2": "RVCv2",
"sd_1": "SD 1.0",
"sd_1.5": "SD 1.5",
"sdxl": "SDXL",
"so_vits_svc": "so_vits_svc",
"tt2": "TT2",
"lora": "loRA",
"vall_e": "vall_e"
},
"MediaTypes": {
"all": "All media",
"audio": "Audio",
"image": "Image",
"video": "Video",
"bvh": "BVH",
"glb": "GLB",
"gltf": "gltf",
"fbx": "FBX"
},
}
@@ -3,16 +3,38 @@
.fy-checkbox-frame {
margin-bottom: .5rem;
white-space: nowrap;
cursor: pointer;
label {
margin-left: .5rem;
cursor: pointer;
}
}
.fy-checkbox-primary-unchecked, .fy-checkbox-primary-checked {
.fy-checkbox {
background-color: $primary;
}
&:hover {
.fy-checkbox {
background-color: lighten($primary, 5%);
}
}
}
.fy-checkbox-secondary-unchecked, .fy-checkbox-secondary-checked {
.fy-checkbox {
background-color: $secondary;
}
&:hover {
.fy-checkbox {
background-color: $secondary-light;
}
}
}
.fy-checkbox {
background-color: $primary;
border-radius: .5rem;
cursor: pointer;
display: inline-block;
vertical-align: middle;
height: 2rem;
@@ -1,7 +1,6 @@
import React from 'react';
import { a, useSpring } from "@react-spring/web";
import { Check } from "components/svg";
import { useHover, useId } from "hooks";
import { useId } from "hooks";
import makeClass from "resources/makeClass";
import "./Checkbox.scss";
@@ -11,20 +10,18 @@ interface Props {
label?: string;
name?: string;
onChange?: any;
variant?: "primary" | "secondary";
}
export default function Checkbox({ checked, className = "", label = "", name = "", onChange }: Props) {
const [hover, hoverEvents = {}] = useHover({});
const style = useSpring({
config: { tension: 120, friction: 14 },
backgroundColor: checked ? hover ? "#bb5a5d" : "#e66462" : hover ? "#bb5a5d" : "#39394c"
});
export default function Checkbox({ checked, className = "", label = "", name = "", onChange, variant = "secondary" }: Props) {
const onClick = ({ target }: any) => onChange({ target: { checked: !checked, name, type: 'checkbox' } });
const id = "checkbox-" + useId();
return <div {...{ ...makeClass("fy-checkbox-frame",className), }}>
<a.svg {...{ className: "fy-checkbox", onClick, style, ...hoverEvents }}>
return <div {...{
...makeClass(`fy-checkbox-frame fy-checkbox-${ variant }-${ checked ? "checked" : "unchecked" }`,className),
}}>
<svg {...{ className: `fy-checkbox`, onClick }}>
<Check {...{ checked }}/>
</a.svg>
</svg>
{ label && <label {...{ className: "form-check-label", htmlFor: id, onClick }}>{ label }</label> }
</div>
};
@@ -11,10 +11,6 @@ interface Props {
required?: boolean;
step?: number;
thumbTip?: string;
// name, value and setter for onChange has to be in this particular format
// name = state variable's name as a stringh
// value = the state variable's reference
// onChange = the statevariable's setter function's reference
name?: string;
value?: number;
onChange?: any;
@@ -29,20 +25,14 @@ const renderTrack = ({ props: { style, ...props }, children }: any) => (
const thumb =
(thumbTip = "") =>
({ props: { style, ...props } }: any) => {
return (
<Tippy
{...{
arrow: false,
content: thumbTip,
placement: "bottom",
theme: "range-slider",
}}
>
<div
{...{ ...props, className: "fy-number-slider-thumb", style }}
></div>
</Tippy>
);
return thumbTip ? <Tippy {...{
arrow: false,
content: thumbTip,
placement: "bottom",
theme: "range-slider",
}}>
<div {...{ ...props, className: "fy-number-slider-thumb", style }}></div>
</Tippy> : <div{...{ ...props, className: "fy-number-slider-thumb", style }}></div>;
};
export default function NumberSlider({
@@ -8,6 +8,21 @@
margin-bottom: 0;
}
.select-container {
overflow: hidden;
}
.select-option {
background: none;
}
.select-option.select__option--is-selected {
background-color: $secondary-lighter;
&.select__option--is-focused {
background-color: $secondary-very-light;
}
}
.select__value-container--is-multi {
height: min-content;
// background: red;
@@ -6,11 +6,15 @@ import "./TempSelect.scss";
type OptionValue = string | number | boolean;
export interface Option {
value: OptionValue;
export interface BaseOption {
value?: OptionValue;
label: string;
}
export interface Option extends BaseOption {
options?: BaseOption[];
}
export interface SelectProps extends ReactSelectProps {
icon?: IconDefinition;
label?: string;
@@ -34,8 +38,21 @@ export default function Select({
...rest
}: SelectProps) {
const isMulti = Array.isArray(value);
const valueLabel =
options.find((option: any) => option.value === value)?.label || "";
const findVal = (opts: Option[], nest = 0): Option | undefined => {
let val: Option | undefined;
opts.forEach((option) => {
if (!val) {
if (option.options) {
val = findVal(option.options,++nest);
} else if (option.value === value) {
val = option
}
}
});
return val;
};
const valueLabel = findVal(options)?.label || "";
const onChange = (option: any, x: any) => {
if (Array.isArray(option)) {
inChange({ target: {value: option.map(({ value = "" }) => value), name, type: "select" } });
@@ -80,9 +97,9 @@ export default function Select({
name,
onChange,
options,
...isMulti ? { value: Array.isArray(value) ?
value.map((val: any) => ({ label: val, value: val })) : [] } :
{ value: { label: valueLabel, value } },
...value && (isMulti ? {
value: Array.isArray(value) ? value.map((val: any) => ({ label: val, value: val })) : []
} : { value: { label: valueLabel, value } }),
...rest,
}}
/>
@@ -132,4 +132,9 @@ export default function EntityInput({ accept: inAccept, aspectRatio = "square",
}
</div>
</>;
};
};
// const handleTargetChange = (ev: React.FormEvent<HTMLSelectElement>) => {
// const value = (ev.target as HTMLSelectElement).value;
// setTarget(value);
// };
@@ -29,10 +29,19 @@ export enum WeightsFilters {
vall_e
}
export enum WeightsCategories {
all,
faceAnimation,
tts,
voiceConversion
}
export type EntityModeProp = keyof typeof EntityInputMode;
export type MediaFilterProp = keyof typeof MediaFilters;
export type WeightFilterProp = keyof typeof WeightsFilters;
export type WeightCategoriesProp = keyof typeof WeightsCategories;
export type AcceptTypes = MediaFilterProp | WeightFilterProp;
export type JobSelection = WeightCategoriesProp | WeightFilterProp;
export enum EntityType {
unknown,
@@ -1,23 +1,35 @@
import React, { useState } from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import PageHeader from 'components/layout/PageHeader';
import { Checkbox, Container, NumberSlider, Panel, TempSelect as Select } from "components/common";
import { WebUrl } from "../../../../../common/WebUrl";
import { BackLink } from "../../../_common/BackLink";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBomb } from "@fortawesome/free-solid-svg-icons";
import {
KillJobs,
} from "@storyteller/components/src/api/moderation/queues/KillJobs";
import { WeightsCategories, WeightsFilters } from "components/entities/EntityTypes";
interface Props {
sessionWrapper: SessionWrapper;
}
function ModerationJobControlPage(props: Props) {
const [filter, filterSet] = useState<string | undefined>();
const objNumKeys = (obj = {}) => Object.keys(obj)
.filter((val: any) => isNaN(Number(val)))
.filter((val: any) => val !== "all");
const weightCategories = objNumKeys(WeightsCategories);
const weightTypes = objNumKeys(WeightsFilters);
const makeOptions = (filters: string[]) => filters.map((filter,i) => ({ label: filter, value: filter }));
const categoryOpts = makeOptions(weightCategories);
const typeOpts = makeOptions(weightTypes);
const groupedOpts = [{ label: "ALL JOBS", value: "all_jobs" },{ label: "Categories", options: categoryOpts },{ label: "Types", options: typeOpts }];
const isCategory = weightCategories.indexOf(filter || "") >= 0;
const [killPendingJobs, setKillPendingJobs] = useState<boolean>(true);
const [killFailedJobs, setKillFailedJobs] = useState<boolean>(true);
const [killStartedJobs, setKillStartedJobs] = useState<boolean>(true);
const [priorityLevel, setPriorityLevel] = useState<number>(0);
const [target, setTarget] = useState<string>('all_jobs');
const killJobs= async () => {
let jobStatuses = [];
@@ -26,30 +38,15 @@ function ModerationJobControlPage(props: Props) {
if (killFailedJobs) { jobStatuses.push('failed') }
if (killStartedJobs) { jobStatuses.push('started') }
// NB: The backend uses parameterized enums, so the request shape here is unusual
let requestTarget: any = 'all_jobs';
switch (target) {
case 'all_jobs':
break;
case 'lipsync_animation':
case 'text_to_speech':
case 'voice_conversion':
requestTarget = { 'category': target };
break;
case 'rvc_v2':
case 'so_vits_svc':
case 'tacotron2':
requestTarget = { 'model_type': target };
break;
}
const request = {
job_statuses: jobStatuses,
target: requestTarget,
target: filter === "all_jobs" ? filter :
{ [ isCategory ? "category" : "model_type" ]: filter }, // *
maybe_priority_or_lower: priorityLevel,
};
// * NB: The backend uses parameterized enums, so the request shape here is unusual
await KillJobs(request);
};
@@ -59,146 +56,77 @@ function ModerationJobControlPage(props: Props) {
return (ev.target as HTMLInputElement).checked;
}
const handlePriorityChange = (ev: React.FormEvent<HTMLInputElement>) => {
const level = (ev.target as HTMLInputElement).value;
let numericLevel = parseInt(level.trim());
if (isNaN(numericLevel)) {
numericLevel = 0;
} else if (numericLevel < 0) {
numericLevel = 0;
}
setPriorityLevel(numericLevel);
};
const handleTargetChange = (ev: React.FormEvent<HTMLSelectElement>) => {
const value = (ev.target as HTMLSelectElement).value;
setTarget(value);
};
if (!props.sessionWrapper.canBanUsers()) {
return <h1>Unauthorized</h1>;
}
const priorityGroups = ["logged out users","free users","loyalty users","premium users"];
let groupList = [...priorityGroups];
groupList.length = priorityLevel + 1;
return (
<div>
<div className="container py-5">
<h1 className=" fw-bold">Job Stats</h1>
<div className="pt-3">
<BackLink link={WebUrl.moderationMain()} text="Back to moderation" />
<Container type="panel" className="mb-5">
<PageHeader {...{
back: { to: WebUrl.moderationMain(), label: "Back to moderation" },
title: "Kill Jobs",
subText: "(Only do this in emergencies!)"
}}/>
<Panel {...{ padding: true }}>
<Select {...{
className: "abc",
label: "Kill a category or weight type",
options: groupedOpts,
onChange: ({ target }) => { filterSet(target.value) },
placeholder: "Select a category or type",
value: filter
}}/>
<h6>With these job statuses</h6>
<div {...{ className: "d-flex gap-3" }}>
<Checkbox {...{
checked: killFailedJobs,
label: "Failed",
onChange: (ev: any) => setKillFailedJobs(checkboxEventStatus(ev))
}}/>
<Checkbox {...{
checked: killPendingJobs,
label: "Pending",
onChange: (ev: any) => setKillPendingJobs(checkboxEventStatus(ev))
}}/>
<Checkbox {...{
checked: killStartedJobs,
label: "Started",
onChange: (ev: any) => setKillStartedJobs(checkboxEventStatus(ev))
}}/>
</div>
</div>
<div className="container-panel pt-3 pb-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">Kill Jobs (Danger Zone)</h2>
<h5 className="fw-semibold mb-4">(Only do this in emergencies!)</h5>
<h5 className="fw-semibold mb-4">Job Types / Categories</h5>
<div>
<label className="sub-title">Category Type</label>
<div className="form-group">
<select
onChange={handleTargetChange}
className="form-select"
value={target}
>
<option value="all_jobs">ALL JOBS !!!</option>
<option value="lipsync_animation">Category: Lipsync Animation</option>
<option value="text_to_speech">Category: Text-to-Speech (TT2, Voice Designer, etc.)</option>
<option value="voice_conversion">Category: Voice Conversion (RVC, SVC, etc.)</option>
<option value="rvc_v2">Model type: RVCv2</option>
<option value="so_vits_svc">Model type: SVC</option>
<option value="tacotron2">Model type: TT2</option>
</select>
</div>
<br />
</div>
<h5 className="fw-semibold mb-4">Job Statuses</h5>
<NumberSlider {...{
label: "For these user priority levels or below",
onChange: ({ target }: { target: any }) => {
setPriorityLevel(target.value);
},
value: priorityLevel,
min: 0,
max: 3
}}/>
<p {...{ className: "mt-3" }}>
This will kill jobs for { groupList.join(", ") }
</p>
<div className="py-6">
<div className="d-flex flex-column gap-3">
<label className="form-check-label">
<input
type="checkbox"
checked={killFailedJobs}
onChange={(ev) => setKillFailedJobs(checkboxEventStatus(ev))}
className="form-check-input"
/>
&nbsp; Kill Failed Jobs
</label>
</div>
<div className="d-flex flex-column gap-3">
<label className="form-check-label">
<input
type="checkbox"
checked={killPendingJobs}
onChange={(ev) => setKillPendingJobs(checkboxEventStatus(ev))}
className="form-check-input"
/>
&nbsp; Kill Pending Jobs
</label>
</div>
<div className="d-flex flex-column gap-3">
<label className="form-check-label">
<input
type="checkbox"
checked={killStartedJobs}
onChange={(ev) => setKillStartedJobs(checkboxEventStatus(ev))}
className="form-check-input"
/>
&nbsp; Kill Started Jobs
</label>
</div>
<br />
<h5 className="fw-semibold mb-4">Job Priorities</h5>
<div>
<label className="sub-title">Kill Priority Level(s) and Below</label>
<div className="form-group">
<input
className="form-control"
type="text"
placeholder="Priority Level"
value={priorityLevel}
onChange={handlePriorityChange}
/>
</div>
<br />
<div>eg. setting this to "3" will kill all priorities 3 and below (3, 2, 1, and 0).</div>
<br />
<ul>
<li>Level 0 = logged out</li>
<li>Level 1 = free account</li>
<li>Level 2+ = premium or loyalty user</li>
</ul>
<br />
<div>Try not to kill premium user workloads unless things get bad.</div>
</div>
<div className="py-6">
<div className="d-flex flex-column gap-3">
<button
className="btn btn-destructive w-100"
onClick={() => killJobs()}
>
Kill Jobs&nbsp;
<FontAwesomeIcon icon={faBomb} />
</button>
</div>
<button
className="btn btn-destructive w-100"
onClick={() => killJobs()}
>
Kill Jobs&nbsp;
<FontAwesomeIcon icon={faBomb} />
</button>
</div>
</div>
</div>
<div className="container pb-5">
<BackLink link={WebUrl.moderationMain()} text="Back to moderation" />
</div>
</div>
</Panel>
</Container>
);
}