diff --git a/gui/src/components/control/ModelControl.tsx b/gui/src/components/control/ModelControl.tsx index dab6c73a..4fe9f6bd 100644 --- a/gui/src/components/control/ModelControl.tsx +++ b/gui/src/components/control/ModelControl.tsx @@ -9,6 +9,7 @@ import { useStore } from 'zustand'; import { STALE_TIME } from '../../config.js'; import { ClientContext, StateContext } from '../../state.js'; import { QueryList } from '../input/QueryList.js'; +import { QueryMenu } from '../input/QueryMenu.js'; export function ModelControl() { const client = mustExist(useContext(ClientContext)); @@ -97,7 +98,7 @@ export function ModelControl() { }} />} /> - result.networks.filter((network) => network.type === 'inversion').map((network) => network.name), }} - value={params.correction} - onChange={(correction) => { - // noop + onSelect={(name) => { + const current = state.getState(); + const { prompt } = current.txt2img; + + current.setTxt2Img({ + prompt: ` ${prompt}`, + }); }} /> - result.networks.filter((network) => network.type === 'lora').map((network) => network.name), }} - value={params.correction} - onChange={(correction) => { - // noop + onSelect={(name) => { + const current = state.getState(); + const { prompt } = current.txt2img; + + current.setTxt2Img({ + prompt: ` ${prompt}`, + }); }} /> ; diff --git a/gui/src/components/input/QueryMenu.tsx b/gui/src/components/input/QueryMenu.tsx new file mode 100644 index 00000000..a5574c51 --- /dev/null +++ b/gui/src/components/input/QueryMenu.tsx @@ -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>; +} + +export interface QueryMenuFilter { + result: UseQueryResult; + selector: (result: T) => Array; +} + +export interface QueryMenuProps { + id: string; + labelKey: string; + name: string; + + query: QueryMenuComplete | QueryMenuFilter; + showEmpty?: boolean; + + onSelect?: (value: string) => void; +} + +export function hasFilter(query: QueryMenuComplete | QueryMenuFilter): query is QueryMenuFilter { + return Reflect.has(query, 'selector'); +} + +export function filterQuery(query: QueryMenuComplete | QueryMenuFilter, showEmpty: boolean): Array { + if (hasFilter(query)) { + const data = mustExist(query.result.data); + const selected = (query as QueryMenuFilter).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(props: QueryMenuProps) { + 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>(undefined); + + function closeMenu() { + setAnchor(undefined); + } + + function openMenu(event: React.MouseEvent) { + 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 {t('input.list.error.specific', { + message: result.error.message, + })}; + } else { + return {t('input.list.error.unknown')}; + } + } + + if (result.status === 'loading') { + return + {props.name} + + ; + } + + if (result.status === 'idle') { + return {t('input.list.idle')}; + } + + // else: success + const data = filterQuery(query, showEmpty); + + return + + + {data.map((it, idx) => selectItem(it)}>{getLabel(it)})} + + ; +} +