feat(gui): add menus to add LoRA and Textual Inversion tokens
This commit is contained in:
parent
a9e55ff9f2
commit
b195b59301
|
@ -9,6 +9,7 @@ import { useStore } from 'zustand';
|
||||||
import { STALE_TIME } from '../../config.js';
|
import { STALE_TIME } from '../../config.js';
|
||||||
import { ClientContext, StateContext } from '../../state.js';
|
import { ClientContext, StateContext } from '../../state.js';
|
||||||
import { QueryList } from '../input/QueryList.js';
|
import { QueryList } from '../input/QueryList.js';
|
||||||
|
import { QueryMenu } from '../input/QueryMenu.js';
|
||||||
|
|
||||||
export function ModelControl() {
|
export function ModelControl() {
|
||||||
const client = mustExist(useContext(ClientContext));
|
const client = mustExist(useContext(ClientContext));
|
||||||
|
@ -97,7 +98,7 @@ export function ModelControl() {
|
||||||
}}
|
}}
|
||||||
/>}
|
/>}
|
||||||
/>
|
/>
|
||||||
<QueryList
|
<QueryMenu
|
||||||
id='inversion'
|
id='inversion'
|
||||||
labelKey='model.inversion'
|
labelKey='model.inversion'
|
||||||
name={t('modelType.inversion')}
|
name={t('modelType.inversion')}
|
||||||
|
@ -105,12 +106,16 @@ export function ModelControl() {
|
||||||
result: models,
|
result: models,
|
||||||
selector: (result) => result.networks.filter((network) => network.type === 'inversion').map((network) => network.name),
|
selector: (result) => result.networks.filter((network) => network.type === 'inversion').map((network) => network.name),
|
||||||
}}
|
}}
|
||||||
value={params.correction}
|
onSelect={(name) => {
|
||||||
onChange={(correction) => {
|
const current = state.getState();
|
||||||
// noop
|
const { prompt } = current.txt2img;
|
||||||
|
|
||||||
|
current.setTxt2Img({
|
||||||
|
prompt: `<inversion:${name}:1.0> ${prompt}`,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<QueryList
|
<QueryMenu
|
||||||
id='lora'
|
id='lora'
|
||||||
labelKey='model.lora'
|
labelKey='model.lora'
|
||||||
name={t('modelType.lora')}
|
name={t('modelType.lora')}
|
||||||
|
@ -118,9 +123,13 @@ export function ModelControl() {
|
||||||
result: models,
|
result: models,
|
||||||
selector: (result) => result.networks.filter((network) => network.type === 'lora').map((network) => network.name),
|
selector: (result) => result.networks.filter((network) => network.type === 'lora').map((network) => network.name),
|
||||||
}}
|
}}
|
||||||
value={params.correction}
|
onSelect={(name) => {
|
||||||
onChange={(correction) => {
|
const current = state.getState();
|
||||||
// noop
|
const { prompt } = current.txt2img;
|
||||||
|
|
||||||
|
current.setTxt2Img({
|
||||||
|
prompt: `<lora:${name}:1.0> ${prompt}`,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>;
|
</Stack>;
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { doesExist, Maybe, mustDefault, mustExist } from '@apextoaster/js-utils';
|
||||||
|
import { KeyboardArrowDown } from '@mui/icons-material';
|
||||||
|
import { Alert, Box, Button, FormControl, FormLabel, LinearProgress, Menu, MenuItem, Typography } from '@mui/material';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { UseQueryResult } from 'react-query';
|
||||||
|
|
||||||
|
export interface QueryMenuComplete {
|
||||||
|
result: UseQueryResult<Array<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryMenuFilter<T> {
|
||||||
|
result: UseQueryResult<T>;
|
||||||
|
selector: (result: T) => Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryMenuProps<T> {
|
||||||
|
id: string;
|
||||||
|
labelKey: string;
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
query: QueryMenuComplete | QueryMenuFilter<T>;
|
||||||
|
showEmpty?: boolean;
|
||||||
|
|
||||||
|
onSelect?: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasFilter<T>(query: QueryMenuComplete | QueryMenuFilter<T>): query is QueryMenuFilter<T> {
|
||||||
|
return Reflect.has(query, 'selector');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterQuery<T>(query: QueryMenuComplete | QueryMenuFilter<T>, showEmpty: boolean): Array<string> {
|
||||||
|
if (hasFilter(query)) {
|
||||||
|
const data = mustExist(query.result.data);
|
||||||
|
const selected = (query as QueryMenuFilter<unknown>).selector(data);
|
||||||
|
if (showEmpty) {
|
||||||
|
return ['', ...selected];
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
} else {
|
||||||
|
const data = Array.from(mustExist(query.result.data));
|
||||||
|
if (showEmpty) {
|
||||||
|
return ['', ...data];
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QueryMenu<T>(props: QueryMenuProps<T>) {
|
||||||
|
const { id, labelKey, name, query, showEmpty = false } = props;
|
||||||
|
const { result } = query;
|
||||||
|
const labelID = `query-menu-${props.id}-labels`;
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [anchor, setAnchor] = React.useState<Maybe<HTMLElement>>(undefined);
|
||||||
|
|
||||||
|
function closeMenu() {
|
||||||
|
setAnchor(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMenu(event: React.MouseEvent<HTMLButtonElement>) {
|
||||||
|
setAnchor(event.currentTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectItem(value: string) {
|
||||||
|
closeMenu();
|
||||||
|
if (doesExist(props.onSelect)) {
|
||||||
|
props.onSelect(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLabel(key: string) {
|
||||||
|
return mustDefault(t(`${labelKey}.${key}`), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.status === 'error') {
|
||||||
|
if (result.error instanceof Error) {
|
||||||
|
return <Alert severity='error'>{t('input.list.error.specific', {
|
||||||
|
message: result.error.message,
|
||||||
|
})}</Alert>;
|
||||||
|
} else {
|
||||||
|
return <Alert severity='error'>{t('input.list.error.unknown')}</Alert>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.status === 'loading') {
|
||||||
|
return <FormControl>
|
||||||
|
<FormLabel id={labelID}>{props.name}</FormLabel>
|
||||||
|
<LinearProgress />
|
||||||
|
</FormControl>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.status === 'idle') {
|
||||||
|
return <Typography>{t('input.list.idle')}</Typography>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// else: success
|
||||||
|
const data = filterQuery(query, showEmpty);
|
||||||
|
|
||||||
|
return <Box>
|
||||||
|
<Button
|
||||||
|
id={`${id}-button`}
|
||||||
|
onClick={openMenu}
|
||||||
|
endIcon={<KeyboardArrowDown />}
|
||||||
|
variant='outlined'
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Button>
|
||||||
|
<Menu
|
||||||
|
id={`${id}-menu`}
|
||||||
|
anchorEl={anchor}
|
||||||
|
open={doesExist(anchor)}
|
||||||
|
onClose={closeMenu}
|
||||||
|
MenuListProps={{
|
||||||
|
'aria-labelledby': `${id}-button`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.map((it, idx) => <MenuItem key={idx} onClick={() => selectItem(it)}>{getLabel(it)}</MenuItem>)}
|
||||||
|
</Menu>
|
||||||
|
</Box>;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue