1
0
Fork 0

feat(gui): add cancel button to loading card

This commit is contained in:
Sean Sube 2023-02-04 11:56:14 -06:00
parent 87bbce4fff
commit f5039d800d
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
7 changed files with 77 additions and 46 deletions

View File

@ -503,7 +503,9 @@ export function makeClient(root: string, f = fetch): ApiClient {
const path = makeApiUrl(root, 'cancel'); const path = makeApiUrl(root, 'cancel');
path.searchParams.append('output', params.output.key); path.searchParams.append('output', params.output.key);
const res = await f(path); const res = await f(path, {
method: 'PUT',
});
return res.status === STATUS_SUCCESS; return res.status === STATUS_SUCCESS;
}, },
}; };

View File

@ -1,7 +1,7 @@
import { doesExist, mustExist } from '@apextoaster/js-utils'; import { doesExist, mustExist } from '@apextoaster/js-utils';
import { Button, Card, CardContent, CircularProgress } from '@mui/material'; import { Box, Button, Card, CardContent, CircularProgress, Typography } from '@mui/material';
import * as React from 'react'; import * as React from 'react';
import { useContext } from 'react'; import { useContext, useEffect } from 'react';
import { useMutation, useQuery } from 'react-query'; import { useMutation, useQuery } from 'react-query';
import { useStore } from 'zustand'; import { useStore } from 'zustand';
@ -9,6 +9,8 @@ import { ImageResponse } from '../client.js';
import { POLL_TIME } from '../config.js'; import { POLL_TIME } from '../config.js';
import { ClientContext, ConfigContext, StateContext } from '../state.js'; import { ClientContext, ConfigContext, StateContext } from '../state.js';
const LOADING_PERCENT = 100;
export interface LoadingCardProps { export interface LoadingCardProps {
loading: ImageResponse; loading: ImageResponse;
} }
@ -22,50 +24,61 @@ export function LoadingCard(props: LoadingCardProps) {
const clearLoading = useStore(state, (s) => s.clearLoading); const clearLoading = useStore(state, (s) => s.clearLoading);
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const pushHistory = useStore(state, (s) => s.pushHistory); const pushHistory = useStore(state, (s) => s.pushHistory);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setReady = useStore(state, (s) => s.setReady);
async function doCancel() { const cancel = useMutation(() => client.cancel(props.loading));
const cancelled = await client.cancel(props.loading); const ready = useQuery('ready', () => client.ready(props.loading), {
if (cancelled) {
clearLoading();
}
}
const cancel = useMutation(doCancel);
const query = useQuery('ready', () => client.ready(props.loading), {
// data will always be ready without this, even if the API says its not // data will always be ready without this, even if the API says its not
cacheTime: 0, cacheTime: 0,
refetchInterval: POLL_TIME, refetchInterval: POLL_TIME,
}); });
function progress() { function getProgress() {
if (doesExist(query.data)) { if (doesExist(ready.data)) {
return Math.ceil(query.data.progress / props.loading.params.steps); return ready.data.progress;
} }
return 0; return 0;
} }
function ready() { function getPercent() {
return doesExist(query.data) && query.data.ready; const pct = getProgress() / props.loading.params.steps;
return Math.ceil(pct * LOADING_PERCENT);
} }
React.useEffect(() => { function getReady() {
if (query.status === 'success' && query.data.ready) { return doesExist(ready.data) && ready.data.ready;
pushHistory(props.loading); }
useEffect(() => {
if (cancel.status === 'success') {
clearLoading();
} }
}, [query.status, ready()]); }, [cancel.status]);
useEffect(() => {
if (ready.status === 'success') {
if (ready.data.ready) {
pushHistory(props.loading);
} else {
setReady(props.loading, ready.data);
}
}
}, [ready.status, getReady(), getProgress()]);
return <Card sx={{ maxWidth: params.width.default }}> return <Card sx={{ maxWidth: params.width.default }}>
<CardContent sx={{ height: params.height.default }}> <CardContent sx={{ height: params.height.default }}>
<div style={{ <Box sx={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
minHeight: params.height.default, minHeight: params.height.default,
}}> }}>
<CircularProgress value={progress()} /> <CircularProgress variant='determinate' value={getPercent()} />
<Button>Cancel</Button> <Typography>{getProgress()} of {props.loading.params.steps}</Typography>
</div> <Button onClick={() => cancel.mutate()}>Cancel</Button>
</Box>
</CardContent> </CardContent>
</Card>; </Card>;
} }

View File

@ -38,7 +38,7 @@ export function Img2Img() {
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const setImg2Img = useStore(state, (s) => s.setImg2Img); const setImg2Img = useStore(state, (s) => s.setImg2Img);
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const setLoading = useStore(state, (s) => s.setLoading); const setLoading = useStore(state, (s) => s.pushLoading);
return <Box> return <Box>
<Stack spacing={2}> <Stack spacing={2}>

View File

@ -62,7 +62,7 @@ export function Inpaint() {
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const setInpaint = useStore(state, (s) => s.setInpaint); const setInpaint = useStore(state, (s) => s.setInpaint);
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const setLoading = useStore(state, (s) => s.setLoading); const setLoading = useStore(state, (s) => s.pushLoading);
const query = useQueryClient(); const query = useQueryClient();
const upload = useMutation(uploadSource, { const upload = useMutation(uploadSource, {

View File

@ -32,7 +32,7 @@ export function Txt2Img() {
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const setTxt2Img = useStore(state, (s) => s.setTxt2Img); const setTxt2Img = useStore(state, (s) => s.setTxt2Img);
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const setLoading = useStore(state, (s) => s.setLoading); const setLoading = useStore(state, (s) => s.pushLoading);
return <Box> return <Box>
<Stack spacing={2}> <Stack spacing={2}>

View File

@ -33,7 +33,7 @@ export function Upscale() {
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const setSource = useStore(state, (s) => s.setUpscaleTab); const setSource = useStore(state, (s) => s.setUpscaleTab);
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const setLoading = useStore(state, (s) => s.setLoading); const setLoading = useStore(state, (s) => s.pushLoading);
return <Box> return <Box>
<Stack spacing={2}> <Stack spacing={2}>

View File

@ -49,9 +49,10 @@ interface HistorySlice {
// TODO: hack until setLoading removes things // TODO: hack until setLoading removes things
clearLoading(): void; clearLoading(): void;
pushHistory(image: ImageResponse): void; pushHistory(image: ImageResponse): void;
pushLoading(image: ImageResponse): void;
removeHistory(image: ImageResponse): void; removeHistory(image: ImageResponse): void;
setLimit(limit: number): void; setLimit(limit: number): void;
setLoading(image: ImageResponse, ready?: Maybe<ReadyResponse>): void; setReady(image: ImageResponse, ready: ReadyResponse): void;
} }
interface ModelSlice { interface ModelSlice {
@ -135,7 +136,7 @@ export const StateContext = createContext<Maybe<StoreApi<OnnxState>>>(undefined)
/** /**
* Current state version for zustand persistence. * Current state version for zustand persistence.
*/ */
export const STATE_VERSION = 4; export const STATE_VERSION = 5;
/** /**
* Default parameters for the inpaint brush. * Default parameters for the inpaint brush.
@ -289,21 +290,20 @@ export function createStateSlices(server: ServerParams) {
loading: [], loading: [],
})); }));
}, },
setLoading(image, ready) { pushLoading(image) {
set((prev) => { set((prev) => ({
const loading = [...prev.loading]; ...prev,
const idx = loading.findIndex((it) => it.image.output.key === image.output.key); loading: [
if (idx >= 0) { {
loading[idx].ready = ready; image,
} else { ready: {
loading.push({ image, ready }); progress: 0,
} ready: false,
},
return { },
...prev, ...prev.loading,
loading, ],
}; }));
});
}, },
removeHistory(image) { removeHistory(image) {
set((prev) => ({ set((prev) => ({
@ -317,6 +317,22 @@ export function createStateSlices(server: ServerParams) {
limit, limit,
})); }));
}, },
setReady(image, ready) {
set((prev) => {
const loading = [...prev.loading];
const idx = loading.findIndex((it) => it.image.output.key === image.output.key);
if (idx >= 0) {
loading[idx].ready = ready;
} else {
// TODO: error
}
return {
...prev,
loading,
};
});
},
}); });
const createOutpaintSlice: Slice<OutpaintSlice> = (set) => ({ const createOutpaintSlice: Slice<OutpaintSlice> = (set) => ({