1
0
Fork 0

feat(gui): add ability to save profiles for img2txt and txt2txt

This commit is contained in:
Ben Fortune 2023-06-30 16:43:35 +01:00
parent 7c8dc7a6b6
commit 4009ed8443
8 changed files with 218 additions and 1 deletions

View File

@ -0,0 +1,125 @@
import * as React from 'react';
import { useContext } from 'react';
import { doesExist, mustExist } from '@apextoaster/js-utils';
import { useStore } from 'zustand';
import { useTranslation } from 'react-i18next';
import {
Button,
IconButton,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
ListItem,
ListItemText,
Autocomplete,
Stack,
} from '@mui/material';
import {
Delete as DeleteIcon,
Save as SaveIcon,
} from '@mui/icons-material';
import { StateContext } from '../state.js';
import { BaseImgParams } from '../client/types.js';
export interface ProfilesProps {
params: BaseImgParams;
setParams: ((params: BaseImgParams) => void) | undefined;
}
export function Profiles(props: ProfilesProps) {
const state = mustExist(useContext(StateContext));
// eslint-disable-next-line @typescript-eslint/unbound-method
const saveProfile = useStore(state, (s) => s.saveProfile);
const removeProfile = useStore(state, (s) => s.removeProfile);
const profiles = useStore(state, (s) => s.profiles);
const highres = useStore(state, (s) => s.highres);
const upscale = useStore(state, (s) => s.upscale);
const [dialogOpen, setDialogOpen] = React.useState(false);
const [profileName, setProfileName] = React.useState('');
const { t } = useTranslation();
return <>
<Autocomplete
id="profile-select"
options={profiles}
sx={{ width: 200 }}
getOptionLabel={(option) => option.name}
clearOnBlur
renderOption={(props, option) => (
<ListItem
{...props}
secondaryAction={
<IconButton edge="end" onClick={(event) => {
event.preventDefault();
removeProfile(option.name);
}}>
<DeleteIcon />
</IconButton>
}
>
<ListItemText primary={option.name} />
</ListItem>
)}
renderInput={(params) => (
<Stack direction="row">
<TextField
{...params}
label={t('profile.load')}
inputProps={{
...params.inputProps,
autoComplete: 'new-password', // disable autocomplete and autofill
}}
/>
<Button type="button" variant="contained" onClick={() => setDialogOpen(true)}>
<SaveIcon />
</Button>
</Stack>
)}
onChange={(event, value) => {
if (value?.params && doesExist(props.setParams)) {
props.setParams({
...value.params
});
}
}}
/>
<Dialog
open={dialogOpen}
onClose={() => setDialogOpen(false)}
>
<DialogTitle>{t('profile.saveProfile')}</DialogTitle>
<DialogContent>
<TextField
variant="standard"
label={t('profile.name')}
value={profileName}
onChange={(event) => setProfileName(event.target.value)}
fullWidth
/>
</DialogContent>
<DialogActions>
<Button
variant='contained'
onClick={() => setDialogOpen(false)}
>{t('profile.cancel')}</Button>
<Button
variant='contained'
onClick={() => {
saveProfile({
params: props.params,
name: profileName,
highResParams: highres,
upscaleParams: upscale,
});
setDialogOpen(false);
setProfileName('');
}}
>{t('profile.save')}</Button>
</DialogActions>
</Dialog>
</>;
}

View File

@ -13,6 +13,7 @@ import { ClientContext, ConfigContext, OnnxState, StateContext } from '../../sta
import { NumericField } from '../input/NumericField.js';
import { PromptInput } from '../input/PromptInput.js';
import { QueryList } from '../input/QueryList.js';
import { Profiles } from '../Profiles.js';
export interface ImageControlProps {
selector: (state: OnnxState) => BaseImgParams;
@ -39,6 +40,7 @@ export function ImageControl(props: ImageControlProps) {
return <Stack spacing={2}>
<Stack direction='row' spacing={4}>
<Profiles params={controlState} setParams={props.onChange} />
<QueryList
id='schedulers'
labelKey='scheduler'

View File

@ -60,6 +60,7 @@ export async function renderApp(config: Config, params: ServerParams, logger: Lo
createBlendSlice,
createResetSlice,
createExtraSlice,
createProfileSlice,
} = createStateSlices(params);
const state = createStore<OnnxState, [['zustand/persist', OnnxState]]>(persist((...slice) => ({
...createBrushSlice(...slice),
@ -75,6 +76,7 @@ export async function renderApp(config: Config, params: ServerParams, logger: Lo
...createBlendSlice(...slice),
...createResetSlice(...slice),
...createExtraSlice(...slice),
...createProfileSlice(...slice),
}), {
name: STATE_KEY,
partialize(s) {

View File

@ -41,6 +41,13 @@ interface HistoryItem {
retry: RetryParams;
}
interface ProfileItem {
name: string;
params: Txt2ImgParams;
highResParams?: Maybe<HighresParams>;
upscaleParams?: Maybe<UpscaleParams>;
}
interface BrushSlice {
brush: BrushParams;
@ -143,6 +150,13 @@ interface BlendSlice {
interface ResetSlice {
resetAll(): void;
}
interface ProfileSlice {
profiles: Array<ProfileItem>;
saveProfile(profile: ProfileItem): void;
removeProfile(profileName: string): void;
}
// #endregion
/**
@ -161,7 +175,8 @@ export type OnnxState
& UpscaleSlice
& BlendSlice
& ResetSlice
& ExtraSlice;
& ExtraSlice
& ProfileSlice;
/**
* Shorthand for state creator to reduce repeated arguments.
@ -572,6 +587,38 @@ export function createStateSlices(server: ServerParams) {
},
});
const createProfileSlice: Slice<ProfileSlice> = (set) => ({
profiles: [],
saveProfile(profile: ProfileItem) {
set((prev) => {
const profiles = [...prev.profiles];
const idx = profiles.findIndex((it) => it.name === profile.name);
if (idx >= 0) {
profiles[idx] = profile;
} else {
profiles.push(profile);
}
return {
...prev,
profiles,
};
});
},
removeProfile(profileName: string) {
set((prev) => {
const profiles = [...prev.profiles];
const idx = profiles.findIndex((it) => it.name === profileName);
if (idx >= 0) {
profiles.splice(idx, 1);
}
return {
...prev,
profiles,
};
});
}
});
// eslint-disable-next-line sonarjs/cognitive-complexity
const createExtraSlice: Slice<ExtraSlice> = (set) => ({
extras: {
@ -765,5 +812,6 @@ export function createStateSlices(server: ServerParams) {
createBlendSlice,
createResetSlice,
createExtraSlice,
createProfileSlice,
};
}

View File

@ -29,6 +29,16 @@ export const I18N_STRINGS_DE = {
type: '',
},
generate: 'Erzeugen',
profile: {
add: '',
saveName: '',
saveProfile: '',
save: '',
saveCurrent: '',
name: '',
cancel: '',
load: '',
},
highresMethod: {
bilinear: '',
lanczos: '',

View File

@ -24,6 +24,16 @@ export const I18N_STRINGS_EN = {
type: 'Type',
},
generate: 'Generate',
profile: {
add: 'Add Profile',
saveName: 'Save Profile Name',
saveProfile: 'Save Profile',
save: 'Save',
saveCurrent: 'Save Current Profile',
name: 'Name',
cancel: 'Cancel',
load: 'Load Profile',
},
highresMethod: {
bilinear: 'Bilinear',
lanczos: 'Lanczos',

View File

@ -29,6 +29,16 @@ export const I18N_STRINGS_ES = {
type: '',
},
generate: 'Generar',
profile: {
add: '',
saveName: '',
saveProfile: '',
save: '',
saveCurrent: '',
name: '',
cancel: '',
load: '',
},
history: {
empty: 'Sin antecedentes recientes. Presiona generar para crear una nueva imagen.',
},

View File

@ -29,6 +29,16 @@ export const I18N_STRINGS_FR = {
type: '',
},
generate: 'générer',
profile: {
add: '',
saveName: '',
saveProfile: '',
save: '',
saveCurrent: '',
name: '',
cancel: '',
load: '',
},
highresMethod: {
bilinear: '',
lanczos: '',