implemented dismiss jobs endpoint (#585)

Co-authored-by: Victor <victor@storyteller.ai>
This commit is contained in:
Victor OandVictor authored and GitHub committed 2024-06-19 16:48:40 -04:00
1 parent 5be986bf7b
commit c326cad2cc
13 files changed
+408 -117

No files matched your search

@@ -0,0 +1,17 @@
import MakeRequest from "../MakeRequest";
export interface DismissFinishedJobsRequest {}
export interface DismissFinishedJobsResponse {
success: boolean;
}
export const DismissFinishedJobs = MakeRequest<
string,
DismissFinishedJobsRequest,
DismissFinishedJobsResponse,
{}
>({
method: "POST",
routingFunction: (jobToken: string) => `/v1/jobs/session/dismiss_finished`,
});
@@ -0,0 +1,52 @@
import React from "react";
import { a, useTransition } from "@react-spring/web";
import { basicTransition } from "resources";
import { Button, Spinner } from "components/common";
import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes";
import { faTrash } from "@fortawesome/pro-solid-svg-icons";
interface Props {
clearJobs?: () => void;
clearJobsStatus?: FetchStatus;
someJobsAreDone?: boolean;
}
export default function JobsClearButton({
clearJobs,
clearJobsStatus,
someJobsAreDone,
}: Props) {
const transitions = useTransition(
// index || 0,
someJobsAreDone ? clearJobsStatus || 0 : 0,
basicTransition({})
);
return (
<div
{...{
className: "fy-clear-jobs-input",
}}
>
{transitions(
(style: any, index) =>
[
<></>,
<a.div {...{ className: "fy-clear-jobs-frame", style }}>
<Button
{...{
icon: faTrash,
onClick: clearJobs,
tooltip: "Clear completed jobs",
variant: "secondary",
square: true,
}}
/>
</a.div>,
<a.div {...{ className: "fy-clear-jobs-frame", style }}>
<Spinner />
</a.div>,
][index]
)}
</div>
);
}
@@ -0,0 +1 @@
export { default } from "./JobsClearButton";
@@ -19,6 +19,7 @@ import FileLabel from "./FileLabel";
import ImageInput from "./ImageInput";
import Input from "./Input";
import InputSeed from "./InputSeed";
import JobsClearButton from "./JobsClearButton";
import JobQueueTicker from "./JobQueueTicker";
import Label from "./Label";
import LikeButton from "./LikeButton";
@@ -83,6 +84,7 @@ export {
ImageInput,
Input,
InputSeed,
JobsClearButton,
JobQueueTicker,
Label,
LikeButton,
@@ -1,19 +1,57 @@
@import "scss/colours.scss";
@import "scss/site-refresh.scss";
$primary-transparent: rgba($primary,.75);
$primary-transparent: rgba($primary, 0.75);
.fy-inference-jobs-list {
overflow: hidden;
header {
display: flex;
justify-content: space-between;
align-items: center;
height: 3rem;
margin-bottom: 1rem;
h3 {
height: 1em;
margin-bottom: 0;
// display: inline;
// background-color: green;
}
}
}
.fy-clear-jobs-input {
position: relative;
height: 100%;
width: 3rem;
right: 0;
.fy-clear-jobs-frame {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
top: 0;
bottom: 0;
right: 0;
left: 0;
// top: 50%;
// transform: translateY(-50%);
}
}
.fy-inference-jobs-list-grid {
display: grid;
grid-template-columns: 2.25rem fit-content(100px) minmax(0, 1fr) minmax(min-content, 1fr);
grid-template-columns: 2.25rem fit-content(100px) minmax(0, 1fr) minmax(
min-content,
1fr
);
gap: 1rem 0;
.work-indicator {
margin-right: -.5rem; // offset for svg gap
margin-right: -0.5rem; // offset for svg gap
}
h6 {
@@ -24,8 +62,9 @@ $primary-transparent: rgba($primary,.75);
white-space: nowrap;
}
.fy-inference-job-hover-on, .fy-inference-job-hover-off {
padding: .5rem;
.fy-inference-job-hover-on,
.fy-inference-job-hover-off {
padding: 0.5rem;
// &:first-of-type { }
}
@@ -39,25 +78,31 @@ $primary-transparent: rgba($primary,.75);
}
.fy-inference-job-indicator {
border-radius: .5rem 0 0 .5rem;
margin-left: -.5rem;
border-radius: 0.5rem 0 0 0.5rem;
margin-left: -0.5rem;
padding-right: 0;
}
.fy-inference-job-action-frame { border-radius: 0 .5rem .5rem 0 }
}
.fy-inference-job-action-frame {
border-radius: 0 0.5rem 0.5rem 0;
}
.fy-inference-job-subtitle, .fy-inference-job-subtitle-success, .job-info-label {
font-size: .875rem;
.fy-inference-job-subtitle,
.fy-inference-job-subtitle-success,
.job-info-label {
font-size: 0.875rem;
font-weight: 400;
white-space: nowrap;
height: 1.125em;
line-height: 1em;
}
.fy-inference-job-subtitle, .job-info-label {
opacity: .6;
.fy-inference-job-subtitle,
.job-info-label {
opacity: 0.6;
}
.fy-inference-job-subtitle, .fy-inference-job-subtitle-success {
.fy-inference-job-subtitle,
.fy-inference-job-subtitle-success {
display: block;
}
@@ -82,14 +127,14 @@ $primary-transparent: rgba($primary,.75);
}
.fy-inference-job-action-frame {
margin-right: -.5rem;
margin-right: -0.5rem;
position: relative;
padding: 0;
// width: fit-content;
// background: rgba(#0f0, .1);
.fy-inference-job-action, .fy-inference-job-action-success {
.fy-inference-job-action,
.fy-inference-job-action-success {
@include medium-svg;
cursor: pointer;
@@ -98,39 +143,46 @@ $primary-transparent: rgba($primary,.75);
// padding-left: .75rem;
// display: flex;
// align-items: center;
border-radius: .5rem;
border-radius: 0.5rem;
// line-height: 1em;
margin: auto 0 auto auto;
}
.job-status-complete-success {
&:hover { background-color: $panel-inner; }
.job-status-complete-success {
&:hover {
background-color: $panel-inner;
}
}
.fy-inference-job-action-success {
background-color: $primary-transparent;
&:hover { background-color: lighten($primary, 5%); }
&:hover {
background-color: lighten($primary, 5%);
}
}
.fy-inference-job-action, .job-stop-confirm, .job-stop-cancel {
.fy-inference-job-action,
.job-stop-confirm,
.job-stop-cancel {
cursor: pointer;
background-color: $secondary;
&:hover { background-color: $secondary-light; }
&:hover {
background-color: $secondary-light;
}
}
.fy-slide-wrapper {
justify-content: flex-end;
display: flex;
align-items: center;
padding-right: .5rem;
padding-right: 0.5rem;
white-space: nowrap;
}
.job-stop-confirm, .job-stop-cancel {
border-radius: .5rem;
margin-left: .5rem;
padding: .625rem 1rem;;
.job-stop-confirm,
.job-stop-cancel {
border-radius: 0.5rem;
margin-left: 0.5rem;
padding: 0.625rem 1rem;
line-height: 1em;
// height: 2.25rem;
}
@@ -138,10 +190,10 @@ $primary-transparent: rgba($primary,.75);
}
.fy-job-queue-ticker {
border-radius: .5rem;
margin-bottom: 1rem;
// padding:1rem;
background-color: rgba(0, 0, 0, .25);
border-radius: 0.5rem;
margin-bottom: 1rem;
// padding:1rem;
background-color: rgba(0, 0, 0, 0.25);
header {
display: flex;
@@ -152,52 +204,101 @@ $primary-transparent: rgba($primary,.75);
h6 {
display: inline;
margin-bottom: 0;
opacity: .66;
opacity: 0.66;
padding: 1rem;
height: 1em;
}
@keyframes tictictic {
00.0% { transform: rotate(0deg); }
04.2% { transform: rotate(15deg); }
08.3% { transform: rotate(30deg); }
12.5% { transform: rotate(45deg); }
16.7% { transform: rotate(60deg); }
20.8% { transform: rotate(75deg); }
25.0% { transform: rotate(90deg); }
29.2% { transform: rotate(105deg); }
33.3% { transform: rotate(120deg); }
37.5% { transform: rotate(135deg); }
41.7% { transform: rotate(150deg); }
45.8% { transform: rotate(165deg); }
50.0% { transform: rotate(180deg); }
54.2% { transform: rotate(195deg); }
58.3% { transform: rotate(210deg); }
62.5% { transform: rotate(225deg); }
66.7% { transform: rotate(240deg); }
70.8% { transform: rotate(255deg); }
75.0% { transform: rotate(270deg); }
79.2% { transform: rotate(285deg); }
83.3% { transform: rotate(300deg); }
87.5% { transform: rotate(315deg); }
91.7% { transform: rotate(330deg); }
95.8% { transform: rotate(345deg); }
100% { transform: rotate(360deg); }
00.0% {
transform: rotate(0deg);
}
04.2% {
transform: rotate(15deg);
}
08.3% {
transform: rotate(30deg);
}
12.5% {
transform: rotate(45deg);
}
16.7% {
transform: rotate(60deg);
}
20.8% {
transform: rotate(75deg);
}
25.0% {
transform: rotate(90deg);
}
29.2% {
transform: rotate(105deg);
}
33.3% {
transform: rotate(120deg);
}
37.5% {
transform: rotate(135deg);
}
41.7% {
transform: rotate(150deg);
}
45.8% {
transform: rotate(165deg);
}
50.0% {
transform: rotate(180deg);
}
54.2% {
transform: rotate(195deg);
}
58.3% {
transform: rotate(210deg);
}
62.5% {
transform: rotate(225deg);
}
66.7% {
transform: rotate(240deg);
}
70.8% {
transform: rotate(255deg);
}
75.0% {
transform: rotate(270deg);
}
79.2% {
transform: rotate(285deg);
}
83.3% {
transform: rotate(300deg);
}
87.5% {
transform: rotate(315deg);
}
91.7% {
transform: rotate(330deg);
}
95.8% {
transform: rotate(345deg);
}
100% {
transform: rotate(360deg);
}
}
.job-cta-clock {
height: 2.625rem;
width: 2.625rem;
fill: none;
margin-left: .25rem;
animation: tictictic 15s cubic-bezier(1,.14,.27,1.51) infinite;
margin-left: 0.25rem;
animation: tictictic 15s cubic-bezier(1, 0.14, 0.27, 1.51) infinite;
.clock-circle {
fill: $primary;
}
.mask-circle {
fill: #fff;
fill: #fff;
}
line {
@@ -213,11 +314,10 @@ $primary-transparent: rgba($primary,.75);
padding: 0 1rem 1rem;
grid-auto-flow: column;
grid-auto-columns: minmax(0, 1fr);
gap: .5rem;
gap: 0.5rem;
* {
display: flex;
* {
display: flex;
flex-direction: column;
justify-content: space-between;
@@ -226,8 +326,8 @@ $primary-transparent: rgba($primary,.75);
max-width: 5rem;
// background-color: greenyellow;
// white-space: nowrap;
font-size: .75rem;
opacity: .5;
font-size: 0.75rem;
opacity: 0.5;
line-height: 1.25em;
}
}
@@ -241,33 +341,41 @@ $primary-transparent: rgba($primary,.75);
border-top: none;
border-right: none;
overflow: hidden;
transition: background-color .5s cubic-bezier(0.45, 0, 0.55, 1);
background-color: rgba($primary,.45);
border-radius: 0 .5rem 0 .5rem;
transition: background-color 0.5s cubic-bezier(0.45, 0, 0.55, 1);
background-color: rgba($primary, 0.45);
border-radius: 0 0.5rem 0 0.5rem;
line-height: 1em;
&:hover {
background-color: rgba($primary,.25);
background-color: rgba($primary, 0.25);
}
.cta-membership-gradient {
background: linear-gradient(90deg, rgba($primary,.0) 0%, rgba($primary,.85) 100%);
padding: .5rem;
background: linear-gradient(
90deg,
rgba($primary, 0) 0%,
rgba($primary, 0.85) 100%
);
padding: 0.5rem;
position: relative;
display: flex;
color: rgba(#fff,.80);
color: rgba(#fff, 0.8);
align-items: center;
.job-cta-message {
line-height: 1.25em;
margin: 0 .5rem 0 .75rem;
&:hover { color: rgba(#fff,.80); }
margin: 0 0.5rem 0 0.75rem;
&:hover {
color: rgba(#fff, 0.8);
}
span {
color: rgba(#fff,.90);
color: rgba(#fff, 0.9);
font-weight: bolder;
text-decoration: underline;
text-shadow: $primary 0 0 6px;
&:hover { color: #fff; }
&:hover {
color: #fff;
}
}
}
}
@@ -279,4 +387,3 @@ $primary-transparent: rgba($primary,.75);
height: 100%;
width: 100%;
}
@@ -4,11 +4,15 @@ import {
FrontendInferenceJobType,
InferenceJob,
} from "@storyteller/components/src/jobs/InferenceJob";
// import { useTransition } from "@react-spring/web";
import JobItem from "./JobItem";
import { useInferenceJobs, useLocalize, useSession } from "hooks";
import "./InferenceJobsList.scss";
import { Button, Panel, JobQueueTicker } from "components/common";
import {
JobsClearButton,
Button,
Panel,
JobQueueTicker,
} from "components/common";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faClipboardList } from "@fortawesome/pro-solid-svg-icons";
@@ -45,19 +49,46 @@ export default function InferenceJobsList({
const { sessionSubscriptions } = useSession();
const hasPaidFeatures = sessionSubscriptions?.hasPaidFeatures();
const {
clearJobs,
clearJobsStatus,
inferenceJobs = [],
inferenceJobsByCategory,
jobStatusDescription,
someJobsAreDone,
} = useInferenceJobs();
const { t } = useLocalize("InferenceJobs");
const selectedJobs =
jobType === undefined || jobType === AllInferenceJobs.All
? inferenceJobs
: inferenceJobsByCategory.get(jobType);
// const { index, ticker } = useInterval({
// debug: "YES",
// end: 2,
// interval: 1000,
// });
const jobContent = (
<>
{showHeader && <h3 className="fw-semibold mb-3">{t("core.heading")}</h3>}
{showHeader && (
<header>
<h3 className="fw-semibold">{t("core.heading")}</h3>
<div
{...{
className: "fy-clear-jobs-input",
}}
>
<JobsClearButton
{...{
clearJobs,
clearJobsStatus,
someJobsAreDone,
}}
/>
</div>
</header>
)}
{showJobQueue && <JobQueueTicker {...{ hasPaidFeatures }} />}
<div {...{ className: "fy-inference-jobs-list-grid" }}>
{selectedJobs &&
@@ -82,8 +113,9 @@ export default function InferenceJobsList({
<h4 className="fw-semibold mb-1">{t("core.noJobsTitle")}</h4>
<p className="opacity-75 mb-2">{t("core.noJobsSubtitle")}</p>
</div>
<Button label={t("core.exploreBtn")} to="/explore" />
<div>
<Button label={t("core.exploreBtn")} to="/explore" />
</div>
</div>
)}
</>
@@ -1,10 +1,10 @@
import React, { useState } from "react"; // useState
import InferenceJobsList from "components/layout/InferenceJobsList";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import { TempSelect as Select } from "components/common";
import { TempSelect as Select, JobsClearButton } from "components/common";
import { enumToKeyArr } from "resources";
import ModalHeader from "../ModalHeader";
import { useLocalize, useSession } from "hooks";
import { useInferenceJobs, useLocalize, useSession } from "hooks";
interface Props {
handleClose?: any;
@@ -19,14 +19,16 @@ export default function InferenceJobsModal({
...rest
}: Props) {
const { canAccessStudio } = useSession();
const { clearJobs, clearJobsStatus, someJobsAreDone } = useInferenceJobs();
const presetFilter = enumToKeyArr(FrontendInferenceJobType)[inJobType];
const [jobType,jobTypeSet] = useState(inJobType > -1 ? presetFilter : "All");
const [jobType, jobTypeSet] = useState(inJobType > -1 ? presetFilter : "All");
const typeObj = ["All", ...Object.values(FrontendInferenceJobType)];
const options = typeObj.filter(val => isNaN(Number(val)))
.map((value) => {
if (typeof value === "string") return { value, label: value }
return { label: "", value: "" };
});
const options = typeObj
.filter(val => isNaN(Number(val)))
.map(value => {
if (typeof value === "string") return { value, label: value };
return { label: "", value: "" };
});
const { t } = useLocalize("InferenceJobs");
const selectedType = typeObj.indexOf(jobType) - 1;
const failures = (fail = "") => {
@@ -38,22 +40,34 @@ export default function InferenceJobsModal({
return (
<>
{ showModalHeader && <ModalHeader {...{ handleClose, title: t("core.jobsTitle") }} /> }
{ canAccessStudio() &&
<Select {...{ onChange: ({ target }: { target: any }) => jobTypeSet(target.value), options, value: jobType }} />
}
{showModalHeader && (
<ModalHeader {...{ handleClose, title: t("core.jobsTitle") }}>
<JobsClearButton
{...{ clearJobs, clearJobsStatus, someJobsAreDone }}
/>
</ModalHeader>
)}
{canAccessStudio() && (
<Select
{...{
onChange: ({ target }: { target: any }) => jobTypeSet(target.value),
options,
value: jobType,
}}
/>
)}
<InferenceJobsList
{...{
failures,
onSelect: () => {
if (handleClose) handleClose();
},
...selectedType > -1 ? { jobType: selectedType } : {},
...(selectedType > -1 ? { jobType: selectedType } : {}),
showHeader: false,
showJobQueue: true,
showNoJobs: true,
panel: false,
...rest
...rest,
}}
/>
</>
@@ -4,6 +4,7 @@
.fy-media-browser-tools {
display: flex;
align-items: center;
margin: auto 0 auto auto;
gap: 1rem;
justify-content: space-between;
@@ -11,11 +12,15 @@
margin: auto 0 auto auto;
}
.fy-temp-select { margin-bottom: 0 }
.fy-temp-select {
margin-bottom: 0;
}
}
.fy-media-browser-header{
h3 { margin-bottom: 0 }
.fy-media-browser-header {
h3 {
margin-bottom: 0;
}
> * {
margin-bottom: 1rem;
}
@@ -28,7 +33,6 @@
@include close-parent;
}
.fy-media-browser-list {
flex-grow: 1;
overflow-y: scroll;
@@ -38,4 +42,4 @@
.fy-media-browser-footer {
margin-top: 1rem;
}
}
@@ -11,13 +11,26 @@ interface Props {
title?: string;
}
export default function ModalHeader({ children, handleClose, onSearchChange = () => {}, search, title }: Props) {
export default function ModalHeader({
children,
handleClose,
onSearchChange = () => {},
search,
title,
}: Props) {
return (
<header {...{ className: "fy-media-browser-header" }}>
<div {...{ className: "fy-media-browser-tools" }}>
{ search !== undefined ? <Input autoFocus {...{ onChange: onSearchChange, value: search }}/> :
title && <h3 className="fw-semibold">{ title }</h3> }
{ handleClose && (
{search !== undefined ? (
<Input autoFocus {...{ onChange: onSearchChange, value: search }} />
) : (
title && <h3 className="fw-semibold">{title}</h3>
)}
{children && (
<div {...{ className: "fy-media-browser-tools" }}>{children}</div>
)}
{handleClose && (
<Icon
{...{
className: "icon-close-button",
@@ -27,9 +40,6 @@ export default function ModalHeader({ children, handleClose, onSearchChange = ()
/>
)}
</div>
{children && (
<div {...{ className: "fy-media-browser-tools" }}>{children}</div>
)}
</header>
);
}
@@ -21,8 +21,14 @@ export default function CoreServicesProvider({
querySubscriptions,
state,
}: Props) {
const { byCategory, enqueueInferenceJob, inferenceJobs } =
useInferenceJobsPolling({ sessionWrapper: state.sessionWrapper });
const {
byCategory,
clearJobs,
clearJobsStatus,
enqueueInferenceJob,
inferenceJobs,
someJobsAreDone,
} = useInferenceJobsPolling({ sessionWrapper: state.sessionWrapper });
const sessionProps = {
querySession: querySession,
@@ -34,7 +40,10 @@ export default function CoreServicesProvider({
const inferenceJobsProps = {
enqueue: enqueueInferenceJob,
byCategory,
clearJobs,
clearJobsStatus,
inferenceJobs,
someJobsAreDone,
};
return (
@@ -3,13 +3,17 @@ import {
BaseQueueObject,
GetQueuesResponse,
} from "@storyteller/components/src/api/stats/queues/GetQueues";
import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes";
import { useQueuePoll } from "hooks";
interface InferenceJobsContextType {
inferenceJobs?: any;
byCategory?: any;
clearJobs?: () => void;
clearJobsStatus?: FetchStatus;
enqueue?: any;
queueStats: GetQueuesResponse;
someJobsAreDone?: boolean;
}
export const InferenceJobsContext = createContext<InferenceJobsContextType>({
@@ -6,14 +6,23 @@ import { useModal } from "hooks";
import { InferenceJobsModal } from "components/modals";
export default function useInferenceJobs(debug = false) {
const { byCategory, enqueue, inferenceJobs, queueStats } =
useContext(InferenceJobsContext);
const {
byCategory,
clearJobs,
clearJobsStatus,
enqueue,
inferenceJobs,
queueStats,
someJobsAreDone,
} = useContext(InferenceJobsContext);
const { open } = useModal();
const openJobListModal = (jobType?: FrontendInferenceJobType) =>
open({ component: InferenceJobsModal, props: { jobType } });
return {
clearJobs,
clearJobsStatus,
enqueue: (
jobToken: string,
jobTypeOverride?: FrontendInferenceJobType,
@@ -30,5 +39,6 @@ export default function useInferenceJobs(debug = false) {
jobStatusDescription: (jobState: JobState) =>
Object.keys(JobState).filter(key => isNaN(Number(key)))[jobState],
queueStats,
someJobsAreDone,
};
}
@@ -13,6 +13,11 @@ import {
GetJobStatus,
GetJobStatusResponse,
} from "@storyteller/components/src/api/model_inference/GetJobStatus";
import {
DismissFinishedJobs,
DismissFinishedJobsResponse,
} from "@storyteller/components/src/api/jobs/DismissFinishedJobs";
import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes";
import { useInterval } from "hooks";
export type CategoryMap = Map<FrontendInferenceJobType, InferenceJob[]>;
@@ -64,6 +69,8 @@ export default function useInferenceJobsPolling({
const [byCategory, byCategorySet] = useState(newJobCategoryMap());
const [initialized, initializedSet] = useState(false);
const [clearJobsStatus, clearJobsStatusSet] = useState(FetchStatus.ready);
// this boolean when set to true starts a useInterval loop, when false it runs clearInterval on that loop
// this is to prevent memory leaks, and to update params provided to useInterval's onTick event.
const [keepAlive, keepAliveSet] = useState(!!user);
@@ -171,6 +178,25 @@ export default function useInferenceJobsPolling({
keepAliveSet(true);
};
const clearJobs = () => {
keepAliveSet(false);
if (clearJobsStatus === FetchStatus.ready) {
clearJobsStatusSet(FetchStatus.in_progress);
DismissFinishedJobs("", {}).then((res: DismissFinishedJobsResponse) => {
if (res.success) {
onTick({ eventProps: { inferenceJobs: [] } });
keepAliveSet(true);
clearJobsStatusSet(FetchStatus.ready);
console.log("🗑️ Finish jobs cleared");
}
});
}
};
const someJobsAreDone =
!!inferenceJobs &&
inferenceJobs.some(job => !jobStateCanChange(job.jobState));
useInterval({
eventProps: { inferenceJobs },
interval,
@@ -180,8 +206,11 @@ export default function useInferenceJobsPolling({
return {
byCategory,
clearJobs,
clearJobsStatus,
inferenceJobsByCategory: byCategory,
enqueueInferenceJob,
inferenceJobs,
someJobsAreDone,
};
}