1
0
Fork 0

continue refactoring to use selectors

This commit is contained in:
Sean Sube 2023-07-22 18:21:54 -05:00
parent 97daf1aa7c
commit 0ba21dfc27
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
20 changed files with 290 additions and 197 deletions

View File

@ -7,7 +7,7 @@ import { useContext, useMemo } from 'react';
import { useHash } from 'react-use/lib/useHash'; import { useHash } from 'react-use/lib/useHash';
import { useStore } from 'zustand'; import { useStore } from 'zustand';
import { StateContext } from '../state.js'; import { OnnxState, StateContext } from '../state.js';
import { ImageHistory } from './ImageHistory.js'; import { ImageHistory } from './ImageHistory.js';
import { Logo } from './Logo.js'; import { Logo } from './Logo.js';
import { Blend } from './tab/Blend.js'; import { Blend } from './tab/Blend.js';
@ -22,7 +22,8 @@ import { getTab, getTheme, TAB_LABELS } from './utils.js';
export function OnnxWeb() { export function OnnxWeb() {
/* checks for system light/dark mode preference */ /* checks for system light/dark mode preference */
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const stateTheme = useStore(mustExist(useContext(StateContext)), (s) => s.theme); const store = mustExist(useContext(StateContext));
const stateTheme = useStore(store, selectTheme);
const theme = useMemo( const theme = useMemo(
() => createTheme({ () => createTheme({
@ -80,3 +81,7 @@ export function OnnxWeb() {
</ThemeProvider> </ThemeProvider>
); );
} }
export function selectTheme(state: OnnxState) {
return state.theme;
}

View File

@ -37,13 +37,9 @@ export interface ProfilesProps {
} }
export function Profiles(props: ProfilesProps) { export function Profiles(props: ProfilesProps) {
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
const profiles = useStore(state, (s) => s.profiles); const { removeProfile, saveProfile } = useStore(store, selectActions);
const profiles = useStore(store, selectProfiles);
// eslint-disable-next-line @typescript-eslint/unbound-method
const saveProfile = useStore(state, (s) => s.saveProfile);
// eslint-disable-next-line @typescript-eslint/unbound-method
const removeProfile = useStore(state, (s) => s.removeProfile);
const [dialogOpen, setDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false);
const [profileName, setProfileName] = useState(''); const [profileName, setProfileName] = useState('');
@ -116,12 +112,12 @@ export function Profiles(props: ProfilesProps) {
<Button <Button
variant='contained' variant='contained'
onClick={() => { onClick={() => {
const innerState = state.getState(); const state = store.getState();
saveProfile({ saveProfile({
params: props.selectParams(innerState), params: props.selectParams(state),
name: profileName, name: profileName,
highres: props.selectHighres(innerState), highres: props.selectHighres(state),
upscale: props.selectUpscale(innerState), upscale: props.selectUpscale(state),
}); });
setDialogOpen(false); setDialogOpen(false);
setProfileName(''); setProfileName('');
@ -161,11 +157,11 @@ export function Profiles(props: ProfilesProps) {
/> />
</Button> </Button>
<Button component='label' variant='contained' onClick={() => { <Button component='label' variant='contained' onClick={() => {
const innerState = state.getState(); const state = store.getState();
downloadParamsAsFile({ downloadParamsAsFile({
params: props.selectParams(innerState), params: props.selectParams(state),
highres: props.selectHighres(innerState), highres: props.selectHighres(state),
upscale: props.selectUpscale(innerState), upscale: props.selectUpscale(state),
}); });
}}> }}>
<Download /> <Download />
@ -173,6 +169,19 @@ export function Profiles(props: ProfilesProps) {
</Stack>; </Stack>;
} }
export function selectActions(state: OnnxState) {
return {
// eslint-disable-next-line @typescript-eslint/unbound-method
removeProfile: state.removeProfile,
// eslint-disable-next-line @typescript-eslint/unbound-method
saveProfile: state.saveProfile,
};
}
export function selectProfiles(state: OnnxState) {
return state.profiles;
}
export async function loadParamsFromFile(file: File): Promise<DeepPartial<ImageMetadata>> { export async function loadParamsFromFile(file: File): Promise<DeepPartial<ImageMetadata>> {
const parts = file.name.toLocaleLowerCase().split('.'); const parts = file.name.toLocaleLowerCase().split('.');
const ext = parts[parts.length - 1]; const ext = parts[parts.length - 1];

View File

@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand'; import { useStore } from 'zustand';
import { ImageResponse, ReadyResponse, RetryParams } from '../../client/types.js'; import { ImageResponse, ReadyResponse, RetryParams } from '../../client/types.js';
import { ClientContext, ConfigContext, StateContext } from '../../state.js'; import { ClientContext, ConfigContext, OnnxState, StateContext } from '../../state.js';
export interface ErrorCardProps { export interface ErrorCardProps {
image: ImageResponse; image: ImageResponse;
@ -20,14 +20,11 @@ export interface ErrorCardProps {
export function ErrorCard(props: ErrorCardProps) { export function ErrorCard(props: ErrorCardProps) {
const { image, ready, retry: retryParams } = props; const { image, ready, retry: retryParams } = props;
const client = mustExist(React.useContext(ClientContext)); const client = mustExist(useContext(ClientContext));
const { params } = mustExist(useContext(ConfigContext)); const { params } = mustExist(useContext(ConfigContext));
const state = mustExist(useContext(StateContext)); const state = mustExist(useContext(StateContext));
// eslint-disable-next-line @typescript-eslint/unbound-method const { pushHistory, removeHistory } = useStore(state, selectActions);
const pushHistory = useStore(state, (s) => s.pushHistory);
// eslint-disable-next-line @typescript-eslint/unbound-method
const removeHistory = useStore(state, (s) => s.removeHistory);
const { t } = useTranslation(); const { t } = useTranslation();
async function retryImage() { async function retryImage() {
@ -72,3 +69,12 @@ export function ErrorCard(props: ErrorCardProps) {
</CardContent> </CardContent>
</Card>; </Card>;
} }
export function selectActions(state: OnnxState) {
return {
// eslint-disable-next-line @typescript-eslint/unbound-method
pushHistory: state.pushHistory,
// eslint-disable-next-line @typescript-eslint/unbound-method
removeHistory: state.removeHistory,
};
}

View File

@ -8,7 +8,7 @@ import { useHash } from 'react-use/lib/useHash';
import { useStore } from 'zustand'; import { useStore } from 'zustand';
import { ImageResponse } from '../../client/types.js'; import { ImageResponse } from '../../client/types.js';
import { BLEND_SOURCES, ConfigContext, StateContext } from '../../state.js'; import { BLEND_SOURCES, ConfigContext, OnnxState, StateContext } from '../../state.js';
import { range, visibleIndex } from '../../utils.js'; import { range, visibleIndex } from '../../utils.js';
export interface ImageCardProps { export interface ImageCardProps {
@ -32,15 +32,8 @@ export function ImageCard(props: ImageCardProps) {
const [saveAnchor, setSaveAnchor] = useState<Maybe<HTMLElement>>(); const [saveAnchor, setSaveAnchor] = useState<Maybe<HTMLElement>>();
const config = mustExist(useContext(ConfigContext)); const config = mustExist(useContext(ConfigContext));
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
// eslint-disable-next-line @typescript-eslint/unbound-method const { setBlend, setImg2Img, setInpaint, setUpscale } = useStore(store, selectActions);
const setImg2Img = useStore(state, (s) => s.setImg2Img);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setInpaint = useStore(state, (s) => s.setInpaint);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setUpscale = useStore(state, (s) => s.setUpscale);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setBlend = useStore(state, (s) => s.setBlend);
async function loadSource() { async function loadSource() {
const req = await fetch(outputs[index].url); const req = await fetch(outputs[index].url);
@ -73,7 +66,7 @@ export function ImageCard(props: ImageCardProps) {
async function copySourceToBlend(idx: number) { async function copySourceToBlend(idx: number) {
const blob = await loadSource(); const blob = await loadSource();
const sources = mustDefault(state.getState().blend.sources, []); const sources = mustDefault(store.getState().blend.sources, []);
const newSources = [...sources]; const newSources = [...sources];
newSources[idx] = blob; newSources[idx] = blob;
setBlend({ setBlend({
@ -229,3 +222,16 @@ export function ImageCard(props: ImageCardProps) {
</CardContent> </CardContent>
</Card>; </Card>;
} }
export function selectActions(state: OnnxState) {
return {
// eslint-disable-next-line @typescript-eslint/unbound-method
setBlend: state.setBlend,
// eslint-disable-next-line @typescript-eslint/unbound-method
setImg2Img: state.setImg2Img,
// eslint-disable-next-line @typescript-eslint/unbound-method
setInpaint: state.setInpaint,
// eslint-disable-next-line @typescript-eslint/unbound-method
setUpscale: state.setUpscale,
};
}

View File

@ -9,7 +9,7 @@ import { useStore } from 'zustand';
import { ImageResponse } from '../../client/types.js'; import { ImageResponse } from '../../client/types.js';
import { POLL_TIME } from '../../config.js'; import { POLL_TIME } from '../../config.js';
import { ClientContext, ConfigContext, StateContext } from '../../state.js'; import { ClientContext, ConfigContext, OnnxState, StateContext } from '../../state.js';
const LOADING_PERCENT = 100; const LOADING_PERCENT = 100;
const LOADING_OVERAGE = 99; const LOADING_OVERAGE = 99;
@ -23,14 +23,11 @@ export function LoadingCard(props: LoadingCardProps) {
const { image, index } = props; const { image, index } = props;
const { steps } = props.image.params; const { steps } = props.image.params;
const client = mustExist(React.useContext(ClientContext)); const client = mustExist(useContext(ClientContext));
const { params } = mustExist(useContext(ConfigContext)); const { params } = mustExist(useContext(ConfigContext));
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
// eslint-disable-next-line @typescript-eslint/unbound-method const { removeHistory, setReady } = useStore(store, selectActions);
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 { t } = useTranslation();
const cancel = useMutation(() => client.cancel(image.outputs[index].key)); const cancel = useMutation(() => client.cancel(image.outputs[index].key));
@ -118,3 +115,12 @@ export function LoadingCard(props: LoadingCardProps) {
</CardContent> </CardContent>
</Card>; </Card>;
} }
export function selectActions(state: OnnxState) {
return {
// eslint-disable-next-line @typescript-eslint/unbound-method
removeHistory: state.removeHistory,
// eslint-disable-next-line @typescript-eslint/unbound-method
setReady: state.setReady,
};
}

View File

@ -18,8 +18,8 @@ export function HighresControl(props: HighresControlProps) {
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const { selectHighres, setHighres } = props; const { selectHighres, setHighres } = props;
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
const highres = useStore(state, selectHighres); const highres = useStore(store, selectHighres);
const { params } = mustExist(useContext(ConfigContext)); const { params } = mustExist(useContext(ConfigContext));
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -2,10 +2,12 @@ import { doesExist, mustDefault, mustExist } from '@apextoaster/js-utils';
import { Casino } from '@mui/icons-material'; import { Casino } from '@mui/icons-material';
import { Button, Checkbox, FormControlLabel, Stack } from '@mui/material'; import { Button, Checkbox, FormControlLabel, Stack } from '@mui/material';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { omit } from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { useContext } from 'react'; import { useContext } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand'; import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { BaseImgParams } from '../../client/types.js'; import { BaseImgParams } from '../../client/types.js';
import { STALE_TIME } from '../../config.js'; import { STALE_TIME } from '../../config.js';
@ -14,20 +16,30 @@ import { NumericField } from '../input/NumericField.js';
import { PromptInput } from '../input/PromptInput.js'; import { PromptInput } from '../input/PromptInput.js';
import { QueryList } from '../input/QueryList.js'; import { QueryList } from '../input/QueryList.js';
const { useMemo } = React;
type BaseParamsWithoutPrompt = Omit<BaseImgParams, 'prompt' | 'negativePrompt'>;
export interface ImageControlProps { export interface ImageControlProps {
onChange(params: BaseImgParams): void; onChange(params: Partial<BaseImgParams>): void;
selector(state: OnnxState): BaseImgParams; selector(state: OnnxState): BaseImgParams;
} }
export function omitPrompt(selector: (state: OnnxState) => BaseImgParams): (state: OnnxState) => BaseParamsWithoutPrompt {
return (state) => omit(selector(state), 'prompt', 'negativePrompt');
}
/** /**
* Doesn't need to use state directly, the parent component knows which params to pass * Doesn't need to use state directly, the parent component knows which params to pass
*/ */
export function ImageControl(props: ImageControlProps) { export function ImageControl(props: ImageControlProps) {
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const { onChange, selector } = props; const { onChange, selector } = props;
const selectOmitPrompt = useMemo(() => omitPrompt(selector), [selector]);
const { params } = mustExist(useContext(ConfigContext)); const { params } = mustExist(useContext(ConfigContext));
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
const controlState = useStore(state, selector); const state = useStore(store, selectOmitPrompt, shallow);
const { t } = useTranslation(); const { t } = useTranslation();
const client = mustExist(useContext(ClientContext)); const client = mustExist(useContext(ClientContext));
@ -36,7 +48,7 @@ export function ImageControl(props: ImageControlProps) {
}); });
// max stride is the lesser of tile size and server's max stride // max stride is the lesser of tile size and server's max stride
const maxStride = Math.min(controlState.tiles, params.stride.max); const maxStride = Math.min(state.tiles, params.stride.max);
return <Stack spacing={2}> return <Stack spacing={2}>
<Stack direction='row' spacing={4}> <Stack direction='row' spacing={4}>
@ -47,11 +59,11 @@ export function ImageControl(props: ImageControlProps) {
query={{ query={{
result: schedulers, result: schedulers,
}} }}
value={mustDefault(controlState.scheduler, '')} value={mustDefault(state.scheduler, '')}
onChange={(value) => { onChange={(value) => {
if (doesExist(onChange)) { if (doesExist(onChange)) {
onChange({ onChange({
...controlState, ...state,
scheduler: value, scheduler: value,
}); });
} }
@ -63,11 +75,11 @@ export function ImageControl(props: ImageControlProps) {
min={params.eta.min} min={params.eta.min}
max={params.eta.max} max={params.eta.max}
step={params.eta.step} step={params.eta.step}
value={controlState.eta} value={state.eta}
onChange={(eta) => { onChange={(eta) => {
if (doesExist(onChange)) { if (doesExist(onChange)) {
onChange({ onChange({
...controlState, ...state,
eta, eta,
}); });
} }
@ -79,11 +91,11 @@ export function ImageControl(props: ImageControlProps) {
min={params.cfg.min} min={params.cfg.min}
max={params.cfg.max} max={params.cfg.max}
step={params.cfg.step} step={params.cfg.step}
value={controlState.cfg} value={state.cfg}
onChange={(cfg) => { onChange={(cfg) => {
if (doesExist(onChange)) { if (doesExist(onChange)) {
onChange({ onChange({
...controlState, ...state,
cfg, cfg,
}); });
} }
@ -94,10 +106,10 @@ export function ImageControl(props: ImageControlProps) {
min={params.steps.min} min={params.steps.min}
max={params.steps.max} max={params.steps.max}
step={params.steps.step} step={params.steps.step}
value={controlState.steps} value={state.steps}
onChange={(steps) => { onChange={(steps) => {
onChange({ onChange({
...controlState, ...state,
steps, steps,
}); });
}} }}
@ -107,10 +119,10 @@ export function ImageControl(props: ImageControlProps) {
min={params.seed.min} min={params.seed.min}
max={params.seed.max} max={params.seed.max}
step={params.seed.step} step={params.seed.step}
value={controlState.seed} value={state.seed}
onChange={(seed) => { onChange={(seed) => {
onChange({ onChange({
...controlState, ...state,
seed, seed,
}); });
}} }}
@ -121,7 +133,7 @@ export function ImageControl(props: ImageControlProps) {
onClick={() => { onClick={() => {
const seed = Math.floor(Math.random() * params.seed.max); const seed = Math.floor(Math.random() * params.seed.max);
props.onChange({ props.onChange({
...controlState, ...state,
seed, seed,
}); });
}} }}
@ -135,10 +147,10 @@ export function ImageControl(props: ImageControlProps) {
min={params.batch.min} min={params.batch.min}
max={params.batch.max} max={params.batch.max}
step={params.batch.step} step={params.batch.step}
value={controlState.batch} value={state.batch}
onChange={(batch) => { onChange={(batch) => {
props.onChange({ props.onChange({
...controlState, ...state,
batch, batch,
}); });
}} }}
@ -148,10 +160,10 @@ export function ImageControl(props: ImageControlProps) {
min={params.tiles.min} min={params.tiles.min}
max={params.tiles.max} max={params.tiles.max}
step={params.tiles.step} step={params.tiles.step}
value={controlState.tiles} value={state.tiles}
onChange={(tiles) => { onChange={(tiles) => {
props.onChange({ props.onChange({
...controlState, ...state,
tiles, tiles,
}); });
}} }}
@ -162,10 +174,10 @@ export function ImageControl(props: ImageControlProps) {
min={params.overlap.min} min={params.overlap.min}
max={params.overlap.max} max={params.overlap.max}
step={params.overlap.step} step={params.overlap.step}
value={controlState.overlap} value={state.overlap}
onChange={(overlap) => { onChange={(overlap) => {
props.onChange({ props.onChange({
...controlState, ...state,
overlap, overlap,
}); });
}} }}
@ -175,10 +187,10 @@ export function ImageControl(props: ImageControlProps) {
min={params.stride.min} min={params.stride.min}
max={maxStride} max={maxStride}
step={params.stride.step} step={params.stride.step}
value={controlState.stride} value={state.stride}
onChange={(stride) => { onChange={(stride) => {
props.onChange({ props.onChange({
...controlState, ...state,
stride, stride,
}); });
}} }}
@ -186,23 +198,22 @@ export function ImageControl(props: ImageControlProps) {
<FormControlLabel <FormControlLabel
label={t('parameter.tiledVAE')} label={t('parameter.tiledVAE')}
control={<Checkbox control={<Checkbox
checked={controlState.tiledVAE} checked={state.tiledVAE}
value='check' value='check'
onChange={(event) => { onChange={(event) => {
props.onChange({ props.onChange({
...controlState, ...state,
tiledVAE: controlState.tiledVAE === false, tiledVAE: state.tiledVAE === false,
}); });
}} }}
/>} />}
/> />
</Stack> </Stack>
<PromptInput <PromptInput
prompt={controlState.prompt} selector={selector}
negativePrompt={controlState.negativePrompt}
onChange={(value) => { onChange={(value) => {
props.onChange({ props.onChange({
...controlState, ...state,
...value, ...value,
}); });
}} }}

View File

@ -5,15 +5,14 @@ import { useContext } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand'; import { useStore } from 'zustand';
import { ConfigContext, StateContext } from '../../state.js'; import { ConfigContext, OnnxState, StateContext } from '../../state.js';
import { NumericField } from '../input/NumericField.js'; import { NumericField } from '../input/NumericField.js';
export function OutpaintControl() { export function OutpaintControl() {
const { params } = mustExist(useContext(ConfigContext)); const { params } = mustExist(useContext(ConfigContext));
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
const outpaint = useStore(state, (s) => s.outpaint); const {setOutpaint} = useStore(store, selectActions);
// eslint-disable-next-line @typescript-eslint/unbound-method const outpaint = useStore(store, selectOutpaint);
const setOutpaint = useStore(state, (s) => s.setOutpaint);
const { t } = useTranslation(); const { t } = useTranslation();
return <Stack direction='row' spacing={4}> return <Stack direction='row' spacing={4}>
@ -83,3 +82,14 @@ export function OutpaintControl() {
/> />
</Stack>; </Stack>;
} }
export function selectActions(state: OnnxState) {
return {
// eslint-disable-next-line @typescript-eslint/unbound-method
setOutpaint: state.setOutpaint,
};
}
export function selectOutpaint(state: OnnxState) {
return state.outpaint;
}

View File

@ -18,8 +18,8 @@ export function UpscaleControl(props: UpscaleControlProps) {
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const { selectUpscale, setUpscale } = props; const { selectUpscale, setUpscale } = props;
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
const upscale = useStore(state, selectUpscale); const upscale = useStore(store, selectUpscale);
const { params } = mustExist(useContext(ConfigContext)); const { params } = mustExist(useContext(ConfigContext));
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -25,8 +25,8 @@ export function EditableList<T>(props: EditableListProps<T>) {
const { newItem, removeItem, renderItem, setItem, selector } = props; const { newItem, removeItem, renderItem, setItem, selector } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
const items = useStore(state, selector); const items = useStore(store, selector);
const [nextLabel, setNextLabel] = useState(''); const [nextLabel, setNextLabel] = useState('');
const [nextSource, setNextSource] = useState(''); const [nextSource, setNextSource] = useState('');
const RenderMemo = useMemo(() => memo(renderItem), [renderItem]); const RenderMemo = useMemo(() => memo(renderItem), [renderItem]);

View File

@ -1,23 +1,28 @@
import { doesExist, mustExist } from '@apextoaster/js-utils'; import { mustExist } from '@apextoaster/js-utils';
import { TextField } from '@mui/material'; import { TextField } from '@mui/material';
import { Stack } from '@mui/system'; import { Stack } from '@mui/system';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import * as React from 'react'; import * as React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { QueryMenu } from '../input/QueryMenu.js';
import { STALE_TIME } from '../../config.js'; import { STALE_TIME } from '../../config.js';
import { ClientContext } from '../../state.js'; import { ClientContext, OnnxState, StateContext } from '../../state.js';
import { QueryMenu } from '../input/QueryMenu.js';
const { useContext } = React; const { useContext } = React;
/**
* @todo replace with a selector
*/
export interface PromptValue { export interface PromptValue {
prompt: string; prompt: string;
negativePrompt?: string; negativePrompt?: string;
} }
export interface PromptInputProps extends PromptValue { export interface PromptInputProps {
onChange: (value: PromptValue) => void; selector(state: OnnxState): PromptValue;
onChange(value: PromptValue): void;
} }
export const PROMPT_GROUP = 75; export const PROMPT_GROUP = 75;
@ -31,16 +36,20 @@ function splitPrompt(prompt: string): Array<string> {
} }
export function PromptInput(props: PromptInputProps) { export function PromptInput(props: PromptInputProps) {
const { prompt = '', negativePrompt = '' } = props; // eslint-disable-next-line @typescript-eslint/unbound-method
const { selector, onChange } = props;
const tokens = splitPrompt(prompt); const store = mustExist(useContext(StateContext));
const groups = Math.ceil(tokens.length / PROMPT_GROUP); const { prompt, negativePrompt } = useStore(store, selector);
const client = mustExist(useContext(ClientContext)); const client = mustExist(useContext(ClientContext));
const models = useQuery(['models'], async () => client.models(), { const models = useQuery(['models'], async () => client.models(), {
staleTime: STALE_TIME, staleTime: STALE_TIME,
}); });
const tokens = splitPrompt(prompt);
const groups = Math.ceil(tokens.length / PROMPT_GROUP);
const { t } = useTranslation(); const { t } = useTranslation();
const helper = t('input.prompt.tokens', { const helper = t('input.prompt.tokens', {
groups, groups,
@ -48,7 +57,7 @@ export function PromptInput(props: PromptInputProps) {
}); });
function addToken(type: string, name: string, weight = 1.0) { function addToken(type: string, name: string, weight = 1.0) {
props.onChange({ onChange({
prompt: `<${type}:${name}:1.0> ${prompt}`, prompt: `<${type}:${name}:1.0> ${prompt}`,
negativePrompt, negativePrompt,
}); });
@ -61,12 +70,10 @@ export function PromptInput(props: PromptInputProps) {
variant='outlined' variant='outlined'
value={prompt} value={prompt}
onChange={(event) => { onChange={(event) => {
if (doesExist(props.onChange)) { props.onChange({
props.onChange({ prompt: event.target.value,
prompt: event.target.value, negativePrompt,
negativePrompt, });
});
}
}} }}
/> />
<TextField <TextField
@ -74,12 +81,10 @@ export function PromptInput(props: PromptInputProps) {
variant='outlined' variant='outlined'
value={negativePrompt} value={negativePrompt}
onChange={(event) => { onChange={(event) => {
if (doesExist(props.onChange)) { props.onChange({
props.onChange({ prompt,
prompt, negativePrompt: event.target.value,
negativePrompt: event.target.value, });
});
}
}} }}
/> />
<Stack direction='row' spacing={2}> <Stack direction='row' spacing={2}>

View File

@ -1,9 +1,9 @@
import { doesExist, Maybe, mustDefault, mustExist } from '@apextoaster/js-utils'; import { doesExist, Maybe, mustDefault, mustExist } from '@apextoaster/js-utils';
import { Alert, FormControl, FormLabel, InputLabel, LinearProgress, MenuItem, Select, Typography } from '@mui/material'; import { Alert, FormControl, FormLabel, InputLabel, LinearProgress, MenuItem, Select } from '@mui/material';
import { UseQueryResult } from '@tanstack/react-query';
import * as React from 'react'; import * as React from 'react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { UseQueryResult } from '@tanstack/react-query';
export interface QueryListComplete { export interface QueryListComplete {
result: UseQueryResult<Array<string>>; result: UseQueryResult<Array<string>>;

View File

@ -1,9 +1,11 @@
import { doesExist, Maybe, mustDefault, mustExist } from '@apextoaster/js-utils'; import { doesExist, Maybe, mustDefault, mustExist } from '@apextoaster/js-utils';
import { KeyboardArrowDown } from '@mui/icons-material'; import { KeyboardArrowDown } from '@mui/icons-material';
import { Alert, Box, Button, FormControl, FormLabel, LinearProgress, Menu, MenuItem, Typography } from '@mui/material'; import { Alert, Box, Button, FormControl, FormLabel, LinearProgress, Menu, MenuItem } from '@mui/material';
import { UseQueryResult } from '@tanstack/react-query';
import * as React from 'react'; import * as React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { UseQueryResult } from '@tanstack/react-query';
const { useState } = React;
export interface QueryMenuComplete { export interface QueryMenuComplete {
result: UseQueryResult<Array<string>>; result: UseQueryResult<Array<string>>;
@ -53,7 +55,7 @@ export function QueryMenu<T>(props: QueryMenuProps<T>) {
const { t } = useTranslation(); const { t } = useTranslation();
const [anchor, setAnchor] = React.useState<Maybe<HTMLElement>>(undefined); const [anchor, setAnchor] = useState<Maybe<HTMLElement>>(undefined);
function closeMenu() { function closeMenu() {
setAnchor(undefined); setAnchor(undefined);

View File

@ -6,7 +6,7 @@ import { useContext } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand'; import { useStore } from 'zustand';
import { BlendParams, ModelParams, UpscaleParams } from '../../client/types.js'; import { BlendParams, BrushParams, ModelParams, UpscaleParams } from '../../client/types.js';
import { IMAGE_FILTER } from '../../config.js'; import { IMAGE_FILTER } from '../../config.js';
import { BLEND_SOURCES, ClientContext, OnnxState, StateContext, TabState } from '../../state.js'; import { BLEND_SOURCES, ClientContext, OnnxState, StateContext, TabState } from '../../state.js';
import { range } from '../../utils.js'; import { range } from '../../utils.js';
@ -16,7 +16,7 @@ import { MaskCanvas } from '../input/MaskCanvas.js';
export function Blend() { export function Blend() {
async function uploadSource() { async function uploadSource() {
const { blend, blendModel, blendUpscale } = state.getState(); const { blend, blendModel, blendUpscale } = store.getState();
const { image, retry } = await client.blend(blendModel, { const { image, retry } = await client.blend(blendModel, {
...blend, ...blend,
mask: mustExist(blend.mask), mask: mustExist(blend.mask),
@ -32,17 +32,10 @@ export function Blend() {
onSuccess: () => query.invalidateQueries(['ready']), onSuccess: () => query.invalidateQueries(['ready']),
}); });
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
const brush = useStore(state, (s) => s.blendBrush); const { pushHistory, setBlend, setBrush, setUpscale } = useStore(store, selectActions);
const blend = useStore(state, selectParams); const brush = useStore(store, selectBrush);
// eslint-disable-next-line @typescript-eslint/unbound-method const blend = useStore(store, selectParams);
const setBlend = useStore(state, (s) => s.setBlend);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setBrush = useStore(state, (s) => s.setBlendBrush);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setUpscale = useStore(state, (s) => s.setBlendUpscale);
// eslint-disable-next-line @typescript-eslint/unbound-method
const pushHistory = useStore(state, (s) => s.pushHistory);
const { t } = useTranslation(); const { t } = useTranslation();
const sources = mustDefault(blend.sources, []); const sources = mustDefault(blend.sources, []);
@ -87,6 +80,23 @@ export function Blend() {
</Box>; </Box>;
} }
export function selectActions(state: OnnxState) {
return {
// eslint-disable-next-line @typescript-eslint/unbound-method
pushHistory: state.pushHistory,
// eslint-disable-next-line @typescript-eslint/unbound-method
setBlend: state.setBlend,
// eslint-disable-next-line @typescript-eslint/unbound-method
setBrush: state.setBlendBrush,
// eslint-disable-next-line @typescript-eslint/unbound-method
setUpscale: state.setBlendUpscale,
};
}
export function selectBrush(state: OnnxState): BrushParams {
return state.blendBrush;
}
export function selectModel(state: OnnxState): ModelParams { export function selectModel(state: OnnxState): ModelParams {
return state.blendModel; return state.blendModel;
} }

View File

@ -23,13 +23,13 @@ export function Img2Img() {
const { params } = mustExist(useContext(ConfigContext)); const { params } = mustExist(useContext(ConfigContext));
async function uploadSource() { async function uploadSource() {
const innerState = state.getState(); const state = store.getState();
const img2img = selectParams(innerState); const img2img = selectParams(state);
const { image, retry } = await client.img2img(model, { const { image, retry } = await client.img2img(model, {
...img2img, ...img2img,
source: mustExist(img2img.source), // TODO: show an error if this doesn't exist source: mustExist(img2img.source), // TODO: show an error if this doesn't exist
}, selectUpscale(innerState), selectHighres(innerState)); }, selectUpscale(state), selectHighres(state));
pushHistory(image, retry); pushHistory(image, retry);
} }
@ -47,10 +47,10 @@ export function Img2Img() {
staleTime: STALE_TIME, staleTime: STALE_TIME,
}); });
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
const { pushHistory, setHighres, setImg2Img, setModel, setUpscale } = useStore(state, selectActions, shallow); const { pushHistory, setHighres, setImg2Img, setModel, setUpscale } = useStore(store, selectActions, shallow);
const { loopback, source, sourceFilter, strength } = useStore(state, selectReactParams, shallow); const { loopback, source, sourceFilter, strength } = useStore(store, selectReactParams, shallow);
const model = useStore(state, selectModel); const model = useStore(store, selectModel);
const { t } = useTranslation(); const { t } = useTranslation();
@ -75,7 +75,7 @@ export function Img2Img() {
}); });
}} }}
/> />
<ImageControl selector={(s) => s.img2img} onChange={setImg2Img} /> <ImageControl selector={selectParams} onChange={setImg2Img} />
<Stack direction='row' spacing={2}> <Stack direction='row' spacing={2}>
<QueryList <QueryList
id='control' id='control'

View File

@ -33,9 +33,9 @@ export function Inpaint() {
}); });
async function uploadSource(): Promise<void> { async function uploadSource(): Promise<void> {
const innerState = state.getState(); const state = store.getState();
const { outpaint } = innerState; const { outpaint } = state;
const inpaint = selectParams(innerState); const inpaint = selectParams(state);
if (outpaint.enabled) { if (outpaint.enabled) {
const { image, retry } = await client.outpaint(model, { const { image, retry } = await client.outpaint(model, {
@ -43,7 +43,7 @@ export function Inpaint() {
...outpaint, ...outpaint,
mask: mustExist(mask), mask: mustExist(mask),
source: mustExist(source), source: mustExist(source),
}, selectUpscale(innerState), selectHighres(innerState)); }, selectUpscale(state), selectHighres(state));
pushHistory(image, retry); pushHistory(image, retry);
} else { } else {
@ -51,7 +51,7 @@ export function Inpaint() {
...inpaint, ...inpaint,
mask: mustExist(mask), mask: mustExist(mask),
source: mustExist(source), source: mustExist(source),
}, selectUpscale(innerState), selectHighres(innerState)); }, selectUpscale(state), selectHighres(state));
pushHistory(image, retry); pushHistory(image, retry);
} }
@ -65,11 +65,11 @@ export function Inpaint() {
return model.model.includes('inpaint'); return model.model.includes('inpaint');
} }
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
const { pushHistory, setBrush, setHighres, setModel, setInpaint, setUpscale } = useStore(state, selectActions, shallow); const { pushHistory, setBrush, setHighres, setModel, setInpaint, setUpscale } = useStore(store, selectActions, shallow);
const { source, mask, strength, noise, filter, tileOrder, fillColor } = useStore(state, selectReactParams, shallow); const { source, mask, strength, noise, filter, tileOrder, fillColor } = useStore(store, selectReactParams, shallow);
const model = useStore(state, selectModel); const model = useStore(store, selectModel);
const brush = useStore(state, selectBrush); const brush = useStore(store, selectBrush);
const { t } = useTranslation(); const { t } = useTranslation();
@ -132,7 +132,7 @@ export function Inpaint() {
setBrush={setBrush} setBrush={setBrush}
/> />
<ImageControl <ImageControl
selector={(s) => s.inpaint} selector={selectParams}
onChange={(newParams) => { onChange={(newParams) => {
setInpaint(newParams); setInpaint(newParams);
}} }}

View File

@ -28,7 +28,7 @@ import { UpscalingModelInput } from '../input/model/UpscalingModel.js';
const { useContext, useEffect } = React; const { useContext, useEffect } = React;
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
const { kebabCase } = _; const { kebabCase } = _;
function mergeModelLists<T extends DiffusionModel | ExtraSource>(local: Array<T>, server: Array<T> = []) { function mergeModelLists<T extends DiffusionModel | ExtraSource>(local: Array<T>, server: Array<T> = []) {
const localNames = new Set(local.map((it) => it.name)); const localNames = new Set(local.map((it) => it.name));
@ -77,44 +77,35 @@ function selectExtraSources(state: OnnxState): Array<ExtraSource> {
} }
export function Models() { export function Models() {
const state = mustExist(React.useContext(StateContext)); const store = mustExist(useContext(StateContext));
// eslint-disable-next-line @typescript-eslint/unbound-method const {
const setExtras = useStore(state, (s) => s.setExtras); setCorrectionModel,
// eslint-disable-next-line @typescript-eslint/unbound-method setDiffusionModel,
const setCorrectionModel = useStore(state, (s) => s.setCorrectionModel); setExtraNetwork,
// eslint-disable-next-line @typescript-eslint/unbound-method setExtraSource,
const setDiffusionModel = useStore(state, (s) => s.setDiffusionModel); setExtras,
// eslint-disable-next-line @typescript-eslint/unbound-method setUpscalingModel,
const setExtraNetwork = useStore(state, (s) => s.setExtraNetwork); removeCorrectionModel,
// eslint-disable-next-line @typescript-eslint/unbound-method removeDiffusionModel,
const setExtraSource = useStore(state, (s) => s.setExtraSource); removeExtraNetwork,
// eslint-disable-next-line @typescript-eslint/unbound-method removeExtraSource,
const setUpscalingModel = useStore(state, (s) => s.setUpscalingModel); removeUpscalingModel,
// eslint-disable-next-line @typescript-eslint/unbound-method } = useStore(store, selectActions);
const removeCorrectionModel = useStore(state, (s) => s.removeCorrectionModel);
// eslint-disable-next-line @typescript-eslint/unbound-method
const removeDiffusionModel = useStore(state, (s) => s.removeDiffusionModel);
// eslint-disable-next-line @typescript-eslint/unbound-method
const removeExtraNetwork = useStore(state, (s) => s.removeExtraNetwork);
// eslint-disable-next-line @typescript-eslint/unbound-method
const removeExtraSource = useStore(state, (s) => s.removeExtraSource);
// eslint-disable-next-line @typescript-eslint/unbound-method
const removeUpscalingModel = useStore(state, (s) => s.removeUpscalingModel);
const client = mustExist(useContext(ClientContext));
const client = mustExist(useContext(ClientContext));
const result = useQuery(['extras'], async () => client.extras(), { const result = useQuery(['extras'], async () => client.extras(), {
staleTime: STALE_TIME, staleTime: STALE_TIME,
}); });
const query = useQueryClient(); const query = useQueryClient();
const write = useMutation(writeExtras, { const write = useMutation(writeExtras, {
onSuccess: () => query.invalidateQueries([ 'extras' ]), onSuccess: () => query.invalidateQueries(['extras']),
}); });
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { useEffect(() => {
if (result.status === 'success' && doesExist(result.data)) { if (result.status === 'success' && doesExist(result.data)) {
setExtras(mergeModels(state.getState().extras, result.data)); setExtras(mergeModels(store.getState().extras, result.data));
} }
}, [result.status]); }, [result.status]);
@ -127,18 +118,18 @@ export function Models() {
if (result.status === 'loading') { if (result.status === 'loading') {
return <Stack spacing={2} direction='row' sx={{ alignItems: 'center' }}> return <Stack spacing={2} direction='row' sx={{ alignItems: 'center' }}>
<CircularProgress /> <CircularProgress />
</Stack> ; </Stack>;
} }
async function writeExtras() { async function writeExtras() {
const resp = await client.writeExtras(state.getState().extras); const resp = await client.writeExtras(store.getState().extras);
// TODO: do something with resp // TODO: do something with resp
} }
return <Stack spacing={2}> return <Stack spacing={2}>
<Accordion> <Accordion>
<AccordionSummary> <AccordionSummary>
{t('modelType.diffusion', {count: 10})} {t('modelType.diffusion', { count: 10 })}
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<EditableList<DiffusionModel> <EditableList<DiffusionModel>
@ -157,7 +148,7 @@ export function Models() {
</Accordion> </Accordion>
<Accordion> <Accordion>
<AccordionSummary> <AccordionSummary>
{t('modelType.correction', {count: 10})} {t('modelType.correction', { count: 10 })}
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<EditableList <EditableList
@ -176,7 +167,7 @@ export function Models() {
</Accordion> </Accordion>
<Accordion> <Accordion>
<AccordionSummary> <AccordionSummary>
{t('modelType.upscaling', {count: 10})} {t('modelType.upscaling', { count: 10 })}
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<EditableList <EditableList
@ -196,7 +187,7 @@ export function Models() {
</Accordion> </Accordion>
<Accordion> <Accordion>
<AccordionSummary> <AccordionSummary>
{t('modelType.network', {count: 10})} {t('modelType.network', { count: 10 })}
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<EditableList <EditableList
@ -217,7 +208,7 @@ export function Models() {
</Accordion> </Accordion>
<Accordion> <Accordion>
<AccordionSummary> <AccordionSummary>
{t('modelType.source', {count: 10})} {t('modelType.source', { count: 10 })}
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<EditableList <EditableList
@ -237,3 +228,30 @@ export function Models() {
<Button color='warning' onClick={() => write.mutate()}>{t('convert')}</Button> <Button color='warning' onClick={() => write.mutate()}>{t('convert')}</Button>
</Stack>; </Stack>;
} }
export function selectActions(state: OnnxState) {
return {
// eslint-disable-next-line @typescript-eslint/unbound-method
setExtras: state.setExtras,
// eslint-disable-next-line @typescript-eslint/unbound-method
setCorrectionModel: state.setCorrectionModel,
// eslint-disable-next-line @typescript-eslint/unbound-method
setDiffusionModel: state.setDiffusionModel,
// eslint-disable-next-line @typescript-eslint/unbound-method
setExtraNetwork: state.setExtraNetwork,
// eslint-disable-next-line @typescript-eslint/unbound-method
setExtraSource: state.setExtraSource,
// eslint-disable-next-line @typescript-eslint/unbound-method
setUpscalingModel: state.setUpscalingModel,
// eslint-disable-next-line @typescript-eslint/unbound-method
removeCorrectionModel: state.removeCorrectionModel,
// eslint-disable-next-line @typescript-eslint/unbound-method
removeDiffusionModel: state.removeDiffusionModel,
// eslint-disable-next-line @typescript-eslint/unbound-method
removeExtraNetwork: state.removeExtraNetwork,
// eslint-disable-next-line @typescript-eslint/unbound-method
removeExtraSource: state.removeExtraSource,
// eslint-disable-next-line @typescript-eslint/unbound-method
removeUpscalingModel: state.removeUpscalingModel,
};
}

View File

@ -20,8 +20,8 @@ export function Txt2Img() {
const { params } = mustExist(useContext(ConfigContext)); const { params } = mustExist(useContext(ConfigContext));
async function generateImage() { async function generateImage() {
const innerState = state.getState(); const state = store.getState();
const { image, retry } = await client.txt2img(model, selectParams(innerState), selectUpscale(innerState), selectHighres(innerState)); const { image, retry } = await client.txt2img(model, selectParams(state), selectUpscale(state), selectHighres(state));
pushHistory(image, retry); pushHistory(image, retry);
} }
@ -32,10 +32,10 @@ export function Txt2Img() {
onSuccess: () => query.invalidateQueries([ 'ready' ]), onSuccess: () => query.invalidateQueries([ 'ready' ]),
}); });
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
const { pushHistory, setHighres, setModel, setParams, setUpscale } = useStore(state, selectActions, shallow); const { pushHistory, setHighres, setModel, setParams, setUpscale } = useStore(store, selectActions, shallow);
const { height, width } = useStore(state, selectReactParams, shallow); const { height, width } = useStore(store, selectReactParams, shallow);
const model = useStore(state, selectModel); const model = useStore(store, selectModel);
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -18,7 +18,7 @@ import { Profiles } from '../Profiles.js';
export function Upscale() { export function Upscale() {
async function uploadSource() { async function uploadSource() {
const { upscaleHighres, upscaleUpscale, upscaleModel, upscale } = state.getState(); const { upscaleHighres, upscaleUpscale, upscaleModel, upscale } = store.getState();
const { image, retry } = await client.upscale(upscaleModel, { const { image, retry } = await client.upscale(upscaleModel, {
...upscale, ...upscale,
source: mustExist(upscale.source), // TODO: show an error if this doesn't exist source: mustExist(upscale.source), // TODO: show an error if this doesn't exist
@ -33,19 +33,10 @@ export function Upscale() {
onSuccess: () => query.invalidateQueries([ 'ready' ]), onSuccess: () => query.invalidateQueries([ 'ready' ]),
}); });
const state = mustExist(useContext(StateContext)); const store = mustExist(useContext(StateContext));
const model = useStore(state, (s) => s.upscaleModel); const { pushHistory, setHighres, setModel, setParams, setUpscale } = useStore(store, selectActions);
const params = useStore(state, (s) => s.upscale); const model = useStore(store, selectModel);
// eslint-disable-next-line @typescript-eslint/unbound-method const params = useStore(store, selectParams);
const setModel = useStore(state, (s) => s.setUpscalingModel);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setHighres = useStore(state, (s) => s.setUpscaleHighres);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setUpscale = useStore(state, (s) => s.setUpscaleUpscale);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setParams = useStore(state, (s) => s.setUpscale);
// eslint-disable-next-line @typescript-eslint/unbound-method
const pushHistory = useStore(state, (s) => s.pushHistory);
const { t } = useTranslation(); const { t } = useTranslation();
return <Box> return <Box>
@ -70,8 +61,7 @@ export function Upscale() {
}} }}
/> />
<PromptInput <PromptInput
prompt={params.prompt} selector={selectParams}
negativePrompt={params.negativePrompt}
onChange={(value) => { onChange={(value) => {
setParams(value); setParams(value);
}} }}
@ -87,6 +77,21 @@ export function Upscale() {
</Box>; </Box>;
} }
export function selectActions(state: OnnxState) {
return {
// eslint-disable-next-line @typescript-eslint/unbound-method
pushHistory: state.pushHistory,
// eslint-disable-next-line @typescript-eslint/unbound-method
setHighres: state.setUpscaleHighres,
// eslint-disable-next-line @typescript-eslint/unbound-method
setModel: state.setUpscaleModel,
// eslint-disable-next-line @typescript-eslint/unbound-method
setParams: state.setUpscale,
// eslint-disable-next-line @typescript-eslint/unbound-method
setUpscale: state.setUpscaleUpscale,
};
}
export function selectModel(state: OnnxState): ModelParams { export function selectModel(state: OnnxState): ModelParams {
return state.upscaleModel; return state.upscaleModel;
} }

View File

@ -35,13 +35,13 @@ export type Theme = PaletteMode | ''; // tri-state, '' is unset
*/ */
export type TabState<TabParams> = ConfigFiles<Required<TabParams>> & ConfigState<Required<TabParams>>; export type TabState<TabParams> = ConfigFiles<Required<TabParams>> & ConfigState<Required<TabParams>>;
interface HistoryItem { export interface HistoryItem {
image: ImageResponse; image: ImageResponse;
ready: Maybe<ReadyResponse>; ready: Maybe<ReadyResponse>;
retry: RetryParams; retry: RetryParams;
} }
interface ProfileItem { export interface ProfileItem {
name: string; name: string;
params: BaseImgParams | Txt2ImgParams; params: BaseImgParams | Txt2ImgParams;
highres?: Maybe<HighresParams>; highres?: Maybe<HighresParams>;