Merge pull request #614 from storytold/feature/job-progress-percentage

added percentage progress to jobs list, removed filters, job queue
This commit is contained in:
Victor O authored and GitHub committed 2024-07-12 19:26:00 -07:00
commit f3663389f7
11 files changed
+198 -118

No files matched your search

@@ -2,43 +2,50 @@ import MakeRequest from "../MakeRequest";
import { UserDetailsLight } from "../_common/UserDetailsLight";
export interface RequestDetails {
inference_category: string,
maybe_model_type?: string,
maybe_model_token?: string,
maybe_model_title?: string,
maybe_raw_inference_text?: string,
inference_category: string;
maybe_model_type?: string;
maybe_model_token?: string;
maybe_model_title?: string;
maybe_raw_inference_text?: string;
}
export interface StatusDetails {
status: string,
maybe_extra_status_description?: string,
maybe_failure_category?: string,
attempt_count: number,
status: string;
maybe_extra_status_description?: string;
maybe_failure_category?: string;
attempt_count: number;
progress_percentage: number;
}
export interface ResultDetails {
entity_type: string,
entity_token: string,
maybe_public_bucket_media_path?: string,
entity_type: string;
entity_token: string;
maybe_public_bucket_media_path?: string;
}
export interface JobStatus {
job_token: string,
request: RequestDetails,
status: StatusDetails,
maybe_result?: ResultDetails,
created_at: Date,
updated_at: Date,
job_token: string;
request: RequestDetails;
status: StatusDetails;
maybe_result?: ResultDetails;
created_at: Date;
updated_at: Date;
}
export interface GetJobStatusRequest {}
export interface GetJobStatusResponse {
success: boolean;
state: JobStatus
state: JobStatus;
}
export const GetJobStatus = MakeRequest<string,GetJobStatusRequest,GetJobStatusResponse,{}>({
export const GetJobStatus = MakeRequest<
string,
GetJobStatusRequest,
GetJobStatusResponse,
{}
>({
method: "GET",
routingFunction: (jobToken: string) =>`/v1/model_inference/job_status/${jobToken}`,
routingFunction: (jobToken: string) =>
`/v1/model_inference/job_status/${jobToken}`,
});
@@ -1,80 +1,88 @@
import { ApiConfig } from "../ApiConfig";
export interface GetModelInferenceJobStatusSuccessResponse {
success: boolean,
state: ModelInferenceJobStatus,
success: boolean;
state: ModelInferenceJobStatus;
}
export interface ModelInferenceJobStatus {
// Job primary key
job_token: string,
job_token: string;
request: RequestDetails,
status: StatusDetails,
maybe_result?: ResultDetails,
created_at: Date,
updated_at: Date,
request: RequestDetails;
status: StatusDetails;
maybe_result?: ResultDetails;
created_at: Date;
updated_at: Date;
}
export interface RequestDetails {
inference_category: string,
inference_category: string;
maybe_model_type?: string,
maybe_model_token?: string,
maybe_model_title?: string,
maybe_model_type?: string;
maybe_model_token?: string;
maybe_model_title?: string;
maybe_raw_inference_text?: string,
maybe_raw_inference_text?: string;
}
export interface StatusDetails {
status: string,
maybe_extra_status_description?: string,
maybe_failure_category?: string,
attempt_count: number,
status: string;
maybe_extra_status_description?: string;
maybe_failure_category?: string;
attempt_count: number;
progress_percentage: number;
}
export interface ResultDetails {
entity_type: string,
entity_token: string,
entity_type: string;
entity_token: string;
maybe_public_bucket_media_path?: string,
maybe_public_bucket_media_path?: string;
}
export interface GetModelInferenceJobStatusErrorResponse {
success: boolean,
success: boolean;
}
export type GetModelInferenceJobStatusResponse = GetModelInferenceJobStatusSuccessResponse | GetModelInferenceJobStatusErrorResponse;
export type GetModelInferenceJobStatusResponse =
| GetModelInferenceJobStatusSuccessResponse
| GetModelInferenceJobStatusErrorResponse;
export function GetModelInferenceJobStatusIsOk(response: GetModelInferenceJobStatusResponse): response is GetModelInferenceJobStatusSuccessResponse {
export function GetModelInferenceJobStatusIsOk(
response: GetModelInferenceJobStatusResponse
): response is GetModelInferenceJobStatusSuccessResponse {
return response?.success === true;
}
export function GetModelInferenceJobStatusIsError(response: GetModelInferenceJobStatusResponse): response is GetModelInferenceJobStatusErrorResponse {
export function GetModelInferenceJobStatusIsError(
response: GetModelInferenceJobStatusResponse
): response is GetModelInferenceJobStatusErrorResponse {
return response?.success === false;
}
export async function GetModelInferenceJobStatus(jobToken: string) : Promise<GetModelInferenceJobStatusResponse>
{
export async function GetModelInferenceJobStatus(
jobToken: string
): Promise<GetModelInferenceJobStatusResponse> {
const endpoint = new ApiConfig().getModelInferenceJobStatus(jobToken);
return fetch(endpoint, {
method: 'GET',
credentials: 'include',
method: "GET",
credentials: "include",
headers: {
'Accept': 'application/json',
Accept: "application/json",
},
})
.then(res => res.json())
.then(res => {
if (res && 'success' in res) {
return res;
} else {
return { success : false };
}
})
.catch(e => {
return { success : false };
});
.then(res => res.json())
.then(res => {
if (res && "success" in res) {
return res;
} else {
return { success: false };
}
})
.catch(e => {
return { success: false };
});
}
@@ -34,6 +34,7 @@ export class InferenceJob {
jobState: JobState;
maybeExtraStatusDescription: string | null;
attemptCount: number;
progressPercentage: number;
// Request
maybeModelType?: string;
@@ -56,6 +57,7 @@ export class InferenceJob {
status: string = "unknown",
maybeExtraStatusDescription: string | null = null,
attemptCount: number = 0,
progressPercentage: number = 0,
// Request
maybeModelType: string | undefined = undefined,
maybeModelToken: string | undefined = undefined,
@@ -72,6 +74,7 @@ export class InferenceJob {
this.jobState = jobStateFromString(status);
this.maybeExtraStatusDescription = maybeExtraStatusDescription;
this.attemptCount = attemptCount;
this.progressPercentage = progressPercentage;
if (!!maybeModelType) {
this.maybeModelTitle = maybeModelType;
@@ -109,6 +112,7 @@ export class InferenceJob {
response.status.status,
response.status.maybe_extra_status_description || null,
response.status.attempt_count || 0,
response.status.progress_percentage || 0,
response.request.maybe_model_type,
response.request.maybe_model_token,
response.request.maybe_model_title,
@@ -87,6 +87,15 @@
"STARTED": "Please wait",
"UNKNOWN": "Please wait or try again"
},
"VideoWorkflow": {
"ATTEMPT_FAILED": "Video Workflow Atempt Failed",
"COMPLETE_FAILURE": "Video Workflow Failed.",
"COMPLETE_SUCCESS": "Video Workflow Complete",
"PENDING": "Pending Video Workflow ",
"STARTED": "Video Workflow In Progress",
"UNKNOWN": "Video Workflow Status Uknown",
"heading": "Video Workflow Jobs"
},
"voice_designer": {
"ATTEMPT_FAILED": "Face Animation Atempt Failed",
"COMPLETE_FAILURE": "Face Animation Failed.",
@@ -96,4 +105,4 @@
"UNKNOWN": "Voice Designer Status Uknown",
"heading": "Voice Designer Jobs"
}
}
}
@@ -36,6 +36,7 @@ const UploadControl = ({ onClick, status }: UploadCtrlProps) => {
<WorkIndicator
{...{
failure,
progressPercentage: 100,
stage: success ? 2 : 1,
success,
}}
@@ -36,9 +36,9 @@ export default function JobsClearButton({
{...{
icon: faTrash,
onClick: clearJobs,
tooltip: "Clear completed jobs",
label: "Clear completed jobs",
variant: "secondary",
square: true,
// square: true,
}}
/>
</a.div>,
@@ -25,7 +25,7 @@ $primary-transparent: rgba($primary, 0.75);
.fy-clear-jobs-input {
position: relative;
height: 100%;
width: 3rem;
// width: 3rem;
right: 0;
.fy-clear-jobs-frame {
@@ -81,6 +81,30 @@ $primary-transparent: rgba($primary, 0.75);
border-radius: 0.5rem 0 0 0.5rem;
margin-left: -0.5rem;
padding-right: 0;
.fy-inference-job-indicator-container {
// background-color: red;
position: relative;
height: fit-content;
}
.fy-inference-job-progress {
text-align: center;
position: absolute;
// background-color: green;
font-size: 0.625rem;
line-height: 0.625rem;
height: 0.625rem;
// height: 100%;
top: 50%;
transform: translateY(-50%);
left: 0;
right: 0;
bottom: 0;
// margin: auto;
// display: inline-block;
// vertical-align: middle;
}
}
.fy-inference-job-action-frame {
border-radius: 0 0.5rem 0.5rem 0;
@@ -10,13 +10,17 @@ import {
CancelJobResponse,
} from "@storyteller/components/src/api/jobs/CancelJob";
import { useHover, useSlides } from "hooks";
import { JobState } from "@storyteller/components/src/jobs/JobStates";
import {
JobState,
jobStateCanChange,
} from "@storyteller/components/src/jobs/JobStates";
import { useHistory } from "react-router-dom";
interface JobListItem extends InferenceJob {
failures: (fail: string) => string;
jobStatusDescription?: any;
onSelect?: any;
progressPercentage: number;
resultPaths: { [key: string]: string };
t?: any;
}
@@ -54,11 +58,13 @@ export default function JobItem({
maybeModelTitle,
maybeResultToken,
onSelect,
progressPercentage,
jobToken,
jobState,
jobStatusDescription,
resultPaths,
t,
...rest
}: JobListItem) {
const history = useHistory();
const [hover, hoverSet = {}] = useHover({});
@@ -118,6 +124,8 @@ export default function JobItem({
}
};
const jobIsAlive = jobStateCanChange(jobState);
const makeBounce = (amount = 0, delay = 0) => ({
delay,
config: { tension: 250, friction: 12 },
@@ -125,6 +133,9 @@ export default function JobItem({
});
const headingBounce = useSpring(makeBounce(8));
const subtitleBounce = useSpring(makeBounce(6, 30));
const percentFade = useSpring({
opacity: jobIsAlive ? 1 : 0,
});
const subtitle = maybeFailureCategory
? `${failures(maybeFailureCategory)}`
: t(`subtitles.${jobStatus}`);
@@ -174,7 +185,16 @@ export default function JobItem({
return (
<>
<div {...{ ...outerProps("fy-inference-job-indicator"), ...hoverSet }}>
<WorkIndicator {...{ failure, stage: dashStatus(), success }} />
<div {...{ className: "fy-inference-job-indicator-container" }}>
<a.div
{...{ className: "fy-inference-job-progress", style: percentFade }}
>
{progressPercentage}%
</a.div>
<WorkIndicator
{...{ failure, progressPercentage, stage: dashStatus(), success }}
/>
</div>
</div>
<div {...{ ...outerProps("fy-inference-job-details"), ...hoverSet }}>
<a.h6 {...{ style: headingBounce }}>
@@ -1,10 +1,9 @@
import React, { useState } from "react"; // useState
import React from "react"; // useState
import InferenceJobsList from "components/layout/InferenceJobsList";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import { TempSelect as Select, JobsClearButton } from "components/common";
import { enumToKeyArr } from "resources";
import { JobsClearButton } from "components/common";
import ModalHeader from "../ModalHeader";
import { useInferenceJobs, useLocalize, useSession } from "hooks";
import { useInferenceJobs, useLocalize } from "hooks";
interface Props {
handleClose?: any;
@@ -18,19 +17,9 @@ export default function InferenceJobsModal({
showModalHeader = true,
...rest
}: Props) {
const { canAccessStudio } = useSession();
const { clearJobs, clearJobsStatus, someJobsAreDone } = useInferenceJobs();
const presetFilter = enumToKeyArr(FrontendInferenceJobType)[inJobType];
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 { t } = useLocalize("InferenceJobs");
const selectedType = typeObj.indexOf(jobType) - 1;
const selectedType = 0;
const failures = (fail = "") => {
switch (fail) {
default:
@@ -47,15 +36,6 @@ export default function InferenceJobsModal({
/>
</ModalHeader>
)}
{canAccessStudio() && (
<Select
{...{
onChange: ({ target }: { target: any }) => jobTypeSet(target.value),
options,
value: jobType,
}}
/>
)}
<InferenceJobsList
{...{
failures,
@@ -64,7 +44,7 @@ export default function InferenceJobsModal({
},
...(selectedType > -1 ? { jobType: selectedType } : {}),
showHeader: false,
showJobQueue: true,
showJobQueue: false,
showNoJobs: true,
panel: false,
...rest,
@@ -1,23 +1,35 @@
import React from 'react';
import React from "react";
import { a, useSpring } from "@react-spring/web";
import { circle } from "../sharedSVGProps";
interface Props {
className?: string,
stage: number
className?: string;
progressPercentage: number;
stage: number;
}
export default function DashedCircle({ className = "", stage = 0 }: Props) {
const dashes = [ // the arity (amount of numbers) must remain the same or react-spring will freakout
"1 7 1 7 1 70", // UKNOWN / PENDING - 3x 1pt strokes with 7 point gaps, then a big 70pt gap
"6 0 6 0 6 70", // STARTED / ATTEMPT_FAILED - 6pt strokes with 0pt gaps to appear as one, big gap
"0 0 50 0 0 0" // COMPLETE_SUCCESS / COMPLETE_FAILURE / DEAD - One big 50pt stroke
export default function DashedCircle({
className = "",
progressPercentage,
stage = 0,
}: Props) {
const max = 87;
const progressBar = (max - 16) * (progressPercentage / 100);
const progressSegment = progressBar / 3;
const dashes = [
// the arity (amount of numbers) must remain the same or react-spring will freakout
`1 7 1 7 1 70`, // UKNOWN / PENDING - 3x 1pt strokes with 7 point gaps, then a big 70pt gap
`${progressSegment} 0 ${progressSegment} 0 ${progressSegment} ${
max - progressBar
}`, // STARTED / ATTEMPT_FAILED - 6pt strokes with 0pt gaps to appear as one, big gap
`0 0 ${max} 0 0 0`, // COMPLETE_SUCCESS / COMPLETE_FAILURE / DEAD - One big 50pt stroke
];
const style = useSpring({
config: { tension: 280, friction: 60 },
opacity: [.5,1,1][stage],
strokeDasharray: dashes[stage]
opacity: [0.5, 1, 1][stage],
strokeDasharray: dashes[stage],
});
return <a.circle {...{ ...circle, className, style }} />;
};
}
@@ -1,19 +1,34 @@
import React from 'react';
import { AniX, Check, DashedCircle } from 'components/svg';
import React from "react";
import { AniX, Check, DashedCircle } from "components/svg";
import { circle } from "../sharedSVGProps";
import "./WorkIndicator.scss";
interface Props {
failure: boolean,
stage: number,
success: boolean
failure: boolean;
progressPercentage: number;
stage: number;
success: boolean;
}
export default function WorkIndicator({ failure = false, stage = 0, success = false, ...rest }: Props) {
return <svg {...{ className: "work-indicator", ...rest }}>
<circle {...{ ...circle, className: "work-indicator-circle-track" }} />
<DashedCircle {...{ className: "work-indicator-circle-marker", stage }}/>
<AniX {...{ checked: failure }}/>
<Check {...{ checked: success }}/>
</svg>;
};
export default function WorkIndicator({
failure = false,
progressPercentage,
stage = 0,
success = false,
...rest
}: Props) {
return (
<svg {...{ className: "work-indicator", ...rest }}>
<circle {...{ ...circle, className: "work-indicator-circle-track" }} />
<DashedCircle
{...{
className: "work-indicator-circle-marker",
progressPercentage,
stage,
}}
/>
<AniX {...{ checked: failure }} />
<Check {...{ checked: success }} />
</svg>
);
}