diff --git a/gui/src/client/api.ts b/gui/src/client/api.ts
index 44414b1c..2f01b241 100644
--- a/gui/src/client/api.ts
+++ b/gui/src/client/api.ts
@@ -175,8 +175,8 @@ export interface ImageResponse {
* Status response from the ready endpoint.
*/
export interface ReadyResponse {
- cancel: boolean;
- error: boolean;
+ cancelled: boolean;
+ failed: boolean;
progress: number;
ready: boolean;
}
diff --git a/gui/src/components/ImageHistory.tsx b/gui/src/components/ImageHistory.tsx
index 713d3509..fad4d2b3 100644
--- a/gui/src/components/ImageHistory.tsx
+++ b/gui/src/components/ImageHistory.tsx
@@ -1,38 +1,45 @@
import { doesExist, mustExist } from '@apextoaster/js-utils';
import { Grid, Typography } from '@mui/material';
-import { useContext } from 'react';
+import { useContext, ReactNode } from 'react';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { StateContext } from '../state.js';
-import { ImageCard } from './ImageCard.js';
-import { LoadingCard } from './LoadingCard.js';
+import { ImageCard } from './card/ImageCard.js';
+import { LoadingCard } from './card/LoadingCard.js';
+import { ErrorCard } from './card/RetryCard.js';
export function ImageHistory() {
const history = useStore(mustExist(useContext(StateContext)), (state) => state.history);
const limit = useStore(mustExist(useContext(StateContext)), (state) => state.limit);
- const loading = useStore(mustExist(useContext(StateContext)), (state) => state.loading);
// eslint-disable-next-line @typescript-eslint/unbound-method
const removeHistory = useStore(mustExist(useContext(StateContext)), (state) => state.removeHistory);
const { t } = useTranslation();
- const children = [];
+ const children: Array<[string, ReactNode]> = [];
- if (loading.length > 0) {
- children.push(...loading.map((item) => ));
+ if (history.length === 0) {
+ children.push(['empty', {t('history.empty')}]);
}
- if (history.length > 0) {
- children.push(...history.map((item) => ));
- } else {
- if (doesExist(loading) === false) {
- children.push({t('history.empty')});
+ const limited = history.slice(0, limit);
+ for (const item of limited) {
+ const key = item.image.outputs[0].key;
+
+ if (doesExist(item.ready) && item.ready.ready) {
+ if (item.ready.cancelled || item.ready.failed) {
+ children.push([key, ]);
+ continue;
+ }
+
+ children.push([key, ]);
+ continue;
}
+
+ children.push([key, ]);
}
- const limited = children.slice(0, limit);
-
- return {limited.map((child, idx) => {child})};
+ return {children.map(([key, child]) => {child})};
}
diff --git a/gui/src/components/ImageCard.tsx b/gui/src/components/card/ImageCard.tsx
similarity index 95%
rename from gui/src/components/ImageCard.tsx
rename to gui/src/components/card/ImageCard.tsx
index a2c03326..10a2dd21 100644
--- a/gui/src/components/ImageCard.tsx
+++ b/gui/src/components/card/ImageCard.tsx
@@ -7,12 +7,12 @@ import { useTranslation } from 'react-i18next';
import { useHash } from 'react-use/lib/useHash';
import { useStore } from 'zustand';
-import { ImageResponse } from '../client/api.js';
-import { BLEND_SOURCES, ConfigContext, StateContext } from '../state.js';
-import { range, visibleIndex } from '../utils.js';
+import { ImageResponse } from '../../client/api.js';
+import { BLEND_SOURCES, ConfigContext, StateContext } from '../../state.js';
+import { range, visibleIndex } from '../../utils.js';
export interface ImageCardProps {
- value: ImageResponse;
+ image: ImageResponse;
onDelete?: (key: ImageResponse) => void;
}
@@ -24,8 +24,8 @@ export function GridItem(props: { xs: number; children: React.ReactNode }) {
}
export function ImageCard(props: ImageCardProps) {
- const { value } = props;
- const { params, outputs, size } = value;
+ const { image } = props;
+ const { params, outputs, size } = image;
const [_hash, setHash] = useHash();
const [anchor, setAnchor] = useState>();
@@ -83,7 +83,7 @@ export function ImageCard(props: ImageCardProps) {
function deleteImage() {
if (doesExist(props.onDelete)) {
- props.onDelete(value);
+ props.onDelete(image);
}
}
diff --git a/gui/src/components/LoadingCard.tsx b/gui/src/components/card/LoadingCard.tsx
similarity index 77%
rename from gui/src/components/LoadingCard.tsx
rename to gui/src/components/card/LoadingCard.tsx
index 311088f8..73083464 100644
--- a/gui/src/components/LoadingCard.tsx
+++ b/gui/src/components/card/LoadingCard.tsx
@@ -7,36 +7,34 @@ import { useTranslation } from 'react-i18next';
import { useMutation, useQuery } from 'react-query';
import { useStore } from 'zustand';
-import { ImageResponse } from '../client/api.js';
-import { POLL_TIME } from '../config.js';
-import { ClientContext, ConfigContext, StateContext } from '../state.js';
+import { ImageResponse } from '../../client/api.js';
+import { POLL_TIME } from '../../config.js';
+import { ClientContext, ConfigContext, StateContext } from '../../state.js';
const LOADING_PERCENT = 100;
const LOADING_OVERAGE = 99;
export interface LoadingCardProps {
+ image: ImageResponse;
index: number;
- loading: ImageResponse;
}
export function LoadingCard(props: LoadingCardProps) {
- const { index, loading } = props;
- const { steps } = props.loading.params;
+ const { image, index } = props;
+ const { steps } = props.image.params;
const client = mustExist(React.useContext(ClientContext));
const { params } = mustExist(useContext(ConfigContext));
const state = mustExist(useContext(StateContext));
// eslint-disable-next-line @typescript-eslint/unbound-method
- const clearLoading = useStore(state, (s) => s.clearLoading);
- // eslint-disable-next-line @typescript-eslint/unbound-method
- const pushHistory = useStore(state, (s) => s.pushHistory);
+ const removeHistory = useStore(state, (s) => s.removeHistory);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setReady = useStore(state, (s) => s.setReady);
const { t } = useTranslation();
- const cancel = useMutation(() => client.cancel(loading.outputs[index].key));
- const ready = useQuery(`ready-${loading.outputs[index].key}`, () => client.ready(loading.outputs[index].key), {
+ const cancel = useMutation(() => client.cancel(image.outputs[index].key));
+ const ready = useQuery(`ready-${image.outputs[index].key}`, () => client.ready(image.outputs[index].key), {
// data will always be ready without this, even if the API says its not
cacheTime: 0,
refetchInterval: POLL_TIME,
@@ -86,17 +84,13 @@ export function LoadingCard(props: LoadingCardProps) {
useEffect(() => {
if (cancel.status === 'success') {
- clearLoading(props.loading);
+ removeHistory(props.image);
}
}, [cancel.status]);
useEffect(() => {
- if (ready.status === 'success') {
- if (ready.data.ready) {
- pushHistory(props.loading);
- } else {
- setReady(props.loading, ready.data);
- }
+ if (ready.status === 'success' && getReady()) {
+ setReady(props.image, ready.data);
}
}, [ready.status, getReady(), getProgress()]);
diff --git a/gui/src/components/card/RetryCard.tsx b/gui/src/components/card/RetryCard.tsx
new file mode 100644
index 00000000..501ce968
--- /dev/null
+++ b/gui/src/components/card/RetryCard.tsx
@@ -0,0 +1,59 @@
+import { mustExist } from '@apextoaster/js-utils';
+import { Box, Button, Card, CardContent, Typography } from '@mui/material';
+import { Stack } from '@mui/system';
+import * as React from 'react';
+import { useContext } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useMutation } from 'react-query';
+import { useStore } from 'zustand';
+
+import { ImageResponse, ReadyResponse } from '../../client/api.js';
+import { ClientContext, ConfigContext, StateContext } from '../../state.js';
+
+export interface ErrorCardProps {
+ image: ImageResponse;
+ ready: ReadyResponse;
+}
+
+export function ErrorCard(props: ErrorCardProps) {
+ const { image, ready } = props;
+
+ const client = mustExist(React.useContext(ClientContext));
+ const { params } = mustExist(useContext(ConfigContext));
+
+ const state = mustExist(useContext(StateContext));
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ const removeHistory = useStore(state, (s) => s.removeHistory);
+ const { t } = useTranslation();
+
+ // TODO: actually retry
+ const retry = useMutation(() => {
+ // eslint-disable-next-line no-console
+ console.log('retry', image);
+ return Promise.resolve(true);
+ });
+
+ return
+
+
+
+ {t('loading.progress', {
+ current: ready.progress,
+ total: image.params.steps,
+ })}
+
+
+
+
+
+ ;
+}
diff --git a/gui/src/components/tab/Blend.tsx b/gui/src/components/tab/Blend.tsx
index a4aed16a..cbeb7d85 100644
--- a/gui/src/components/tab/Blend.tsx
+++ b/gui/src/components/tab/Blend.tsx
@@ -23,7 +23,7 @@ export function Blend() {
sources: mustExist(blend.sources), // TODO: show an error if this doesn't exist
}, upscale);
- setLoading(output);
+ pushHistory(output);
}
const client = mustExist(useContext(ClientContext));
@@ -37,7 +37,7 @@ export function Blend() {
// eslint-disable-next-line @typescript-eslint/unbound-method
const setBlend = useStore(state, (s) => s.setBlend);
// eslint-disable-next-line @typescript-eslint/unbound-method
- const setLoading = useStore(state, (s) => s.pushLoading);
+ const pushHistory = useStore(state, (s) => s.pushHistory);
const { t } = useTranslation();
const sources = mustDefault(blend.sources, []);
diff --git a/gui/src/components/tab/Img2Img.tsx b/gui/src/components/tab/Img2Img.tsx
index 1c5b393c..1d89973e 100644
--- a/gui/src/components/tab/Img2Img.tsx
+++ b/gui/src/components/tab/Img2Img.tsx
@@ -24,7 +24,7 @@ export function Img2Img() {
source: mustExist(img2img.source), // TODO: show an error if this doesn't exist
}, upscale);
- setLoading(output);
+ pushHistory(output);
}
const client = mustExist(useContext(ClientContext));
@@ -39,7 +39,7 @@ export function Img2Img() {
// eslint-disable-next-line @typescript-eslint/unbound-method
const setImg2Img = useStore(state, (s) => s.setImg2Img);
// eslint-disable-next-line @typescript-eslint/unbound-method
- const setLoading = useStore(state, (s) => s.pushLoading);
+ const pushHistory = useStore(state, (s) => s.pushHistory);
const { t } = useTranslation();
return
diff --git a/gui/src/components/tab/Inpaint.tsx b/gui/src/components/tab/Inpaint.tsx
index becb4409..20c935ab 100644
--- a/gui/src/components/tab/Inpaint.tsx
+++ b/gui/src/components/tab/Inpaint.tsx
@@ -39,7 +39,7 @@ export function Inpaint() {
source: mustExist(source),
}, upscale);
- setLoading(output);
+ pushHistory(output);
} else {
const output = await client.inpaint(model, {
...inpaint,
@@ -47,7 +47,7 @@ export function Inpaint() {
source: mustExist(source),
}, upscale);
- setLoading(output);
+ pushHistory(output);
}
}
@@ -72,7 +72,7 @@ export function Inpaint() {
// eslint-disable-next-line @typescript-eslint/unbound-method
const setInpaint = useStore(state, (s) => s.setInpaint);
// eslint-disable-next-line @typescript-eslint/unbound-method
- const setLoading = useStore(state, (s) => s.pushLoading);
+ const pushHistory = useStore(state, (s) => s.pushHistory);
const { t } = useTranslation();
const query = useQueryClient();
diff --git a/gui/src/components/tab/Txt2Img.tsx b/gui/src/components/tab/Txt2Img.tsx
index 0b735fe2..4990f222 100644
--- a/gui/src/components/tab/Txt2Img.tsx
+++ b/gui/src/components/tab/Txt2Img.tsx
@@ -18,7 +18,7 @@ export function Txt2Img() {
const { model, txt2img, upscale } = state.getState();
const output = await client.txt2img(model, txt2img, upscale);
- setLoading(output);
+ pushHistory(output);
}
const client = mustExist(useContext(ClientContext));
@@ -33,7 +33,7 @@ export function Txt2Img() {
// eslint-disable-next-line @typescript-eslint/unbound-method
const setTxt2Img = useStore(state, (s) => s.setTxt2Img);
// eslint-disable-next-line @typescript-eslint/unbound-method
- const setLoading = useStore(state, (s) => s.pushLoading);
+ const pushHistory = useStore(state, (s) => s.pushHistory);
const { t } = useTranslation();
return
diff --git a/gui/src/components/tab/Upscale.tsx b/gui/src/components/tab/Upscale.tsx
index 20243f83..9faef6b6 100644
--- a/gui/src/components/tab/Upscale.tsx
+++ b/gui/src/components/tab/Upscale.tsx
@@ -21,7 +21,7 @@ export function Upscale() {
source: mustExist(params.source), // TODO: show an error if this doesn't exist
}, upscale);
- setLoading(output);
+ pushHistory(output);
}
const client = mustExist(useContext(ClientContext));
@@ -35,7 +35,7 @@ export function Upscale() {
// eslint-disable-next-line @typescript-eslint/unbound-method
const setSource = useStore(state, (s) => s.setUpscaleTab);
// eslint-disable-next-line @typescript-eslint/unbound-method
- const setLoading = useStore(state, (s) => s.pushLoading);
+ const pushHistory = useStore(state, (s) => s.pushHistory);
const { t } = useTranslation();
return
diff --git a/gui/src/state.ts b/gui/src/state.ts
index 9c93d93f..2a73670b 100644
--- a/gui/src/state.ts
+++ b/gui/src/state.ts
@@ -27,7 +27,7 @@ import { Config, ConfigFiles, ConfigState, ServerParams } from './config.js';
*/
type TabState = ConfigFiles> & ConfigState>;
-interface LoadingItem {
+interface HistoryItem {
image: ImageResponse;
ready: Maybe;
}
@@ -45,18 +45,12 @@ interface DefaultSlice {
}
interface HistorySlice {
- history: Array;
+ history: Array;
limit: number;
- loading: Array;
- clearLoading(image: ImageResponse): void;
pushHistory(image: ImageResponse): void;
- pushLoading(image: ImageResponse): void;
removeHistory(image: ImageResponse): void;
setLimit(limit: number): void;
- /**
- * @todo should check ready and move the image from loading to history
- */
setReady(image: ImageResponse, ready: ReadyResponse): void;
}
@@ -164,7 +158,7 @@ export const STATE_KEY = 'onnx-web';
/**
* Current state version for zustand persistence.
*/
-export const STATE_VERSION = 6;
+export const STATE_VERSION = 7;
export const BLEND_SOURCES = 2;
@@ -307,42 +301,27 @@ export function createStateSlices(server: ServerParams) {
const createHistorySlice: Slice = (set) => ({
history: [],
limit: DEFAULT_HISTORY.limit,
- loading: [],
- clearLoading(image) {
- set((prev) => ({
- ...prev,
- loading: prev.loading.filter((it) => it.image.outputs[0].key !== image.outputs[0].key),
- }));
- },
pushHistory(image) {
set((prev) => ({
...prev,
history: [
- image,
- ...prev.history,
- ].slice(0, prev.limit + DEFAULT_HISTORY.scrollback),
- loading: prev.loading.filter((it) => it.image.outputs[0].key !== image.outputs[0].key),
- }));
- },
- pushLoading(image) {
- set((prev) => ({
- ...prev,
- loading: [
{
image,
ready: {
+ cancelled: false,
+ failed: false,
progress: 0,
ready: false,
},
},
- ...prev.loading,
+ ...prev.history,
],
}));
},
removeHistory(image) {
set((prev) => ({
...prev,
- history: prev.history.filter((it) => it.outputs !== image.outputs),
+ history: prev.history.filter((it) => it.image.outputs[0].key !== image.outputs[0].key),
}));
},
setLimit(limit) {
@@ -353,17 +332,17 @@ export function createStateSlices(server: ServerParams) {
},
setReady(image, ready) {
set((prev) => {
- const loading = [...prev.loading];
- const idx = loading.findIndex((it) => it.image.outputs[0].key === image.outputs[0].key);
+ const history = [...prev.history];
+ const idx = history.findIndex((it) => it.image.outputs[0].key === image.outputs[0].key);
if (idx >= 0) {
- loading[idx].ready = ready;
+ history[idx].ready = ready;
} else {
// TODO: error
}
return {
...prev,
- loading,
+ history,
};
});
},