feat(gui): add ability to save profiles for img2txt and txt2txt
This commit is contained in:
parent
7c8dc7a6b6
commit
4009ed8443
|
@ -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>
|
||||
</>;
|
||||
}
|
|
@ -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'
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -29,6 +29,16 @@ export const I18N_STRINGS_DE = {
|
|||
type: '',
|
||||
},
|
||||
generate: 'Erzeugen',
|
||||
profile: {
|
||||
add: '',
|
||||
saveName: '',
|
||||
saveProfile: '',
|
||||
save: '',
|
||||
saveCurrent: '',
|
||||
name: '',
|
||||
cancel: '',
|
||||
load: '',
|
||||
},
|
||||
highresMethod: {
|
||||
bilinear: '',
|
||||
lanczos: '',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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.',
|
||||
},
|
||||
|
|
|
@ -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: '',
|
||||
|
|
Loading…
Reference in New Issue