1
0
Fork 0

clean up horizontal layout styles, use constants for spacing

This commit is contained in:
Sean Sube 2024-01-12 19:01:15 -06:00
parent f5506b17f0
commit f30c5f2d31
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
37 changed files with 205 additions and 220 deletions

View File

@ -2,6 +2,8 @@
import { doesExist, InvalidArgumentError, Maybe } from '@apextoaster/js-utils';
import { ServerParams } from '../config.js';
import { FIXED_FLOAT, FIXED_INTEGER, STATUS_SUCCESS } from '../constants.js';
import { JobResponse, JobResponseWithRetry, SuccessJobResponse } from '../types/api-v2.js';
import {
FilterResponse,
ModelResponse,
@ -24,22 +26,6 @@ import {
} from '../types/params.js';
import { range } from '../utils.js';
import { ApiClient } from './base.js';
import { JobResponse, JobResponseWithRetry, SuccessJobResponse } from '../types/api-v2.js';
/**
* Fixed precision for integer parameters.
*/
export const FIXED_INTEGER = 0;
/**
* Fixed precision for float parameters.
*
* The GUI limits the input steps based on the server parameters, but this does limit
* the maximum precision that can be sent back to the server, and may have to be
* increased in the future.
*/
export const FIXED_FLOAT = 2;
export const STATUS_SUCCESS = 200;
export function equalResponse(a: JobResponse, b: JobResponse): boolean {
return a.name === b.name;

View File

@ -1,16 +1,17 @@
import { mustExist } from '@apextoaster/js-utils';
import { Grid, Typography } from '@mui/material';
import { ReactNode, useContext } from 'react';
import * as React from 'react';
import { ReactNode, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { STANDARD_SPACING } from '../constants.js';
import { OnnxState, StateContext } from '../state/full.js';
import { JobStatus } from '../types/api-v2.js';
import { ErrorCard } from './card/ErrorCard.js';
import { ImageCard } from './card/ImageCard.js';
import { LoadingCard } from './card/LoadingCard.js';
import { JobStatus } from '../types/api-v2.js';
export interface ImageHistoryProps {
width: number;
@ -50,7 +51,7 @@ export function ImageHistory(props: ImageHistoryProps) {
return <Grid
container
spacing={2}
spacing={STANDARD_SPACING}
>{
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
children.map(([key, child]) => <Grid item key={key} xs={12 / width}>{child}</Grid>)

View File

@ -2,6 +2,8 @@ import { Box, CircularProgress, Stack, Typography } from '@mui/material';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { STANDARD_SPACING } from '../constants';
export function LoadingScreen() {
const { t } = useTranslation();
@ -13,7 +15,7 @@ export function LoadingScreen() {
}}>
<Stack
direction='column'
spacing={2}
spacing={STANDARD_SPACING}
sx={{ alignItems: 'center' }}
>
<CircularProgress />

View File

@ -2,6 +2,7 @@ import { Box, Button, Container, Stack, Typography } from '@mui/material';
import * as React from 'react';
import { ReactNode } from 'react';
import { STANDARD_MARGIN, STANDARD_SPACING } from '../constants.js';
import { STATE_KEY } from '../state/full.js';
import { Logo } from './Logo.js';
@ -20,11 +21,11 @@ export function OnnxError(props: OnnxErrorProps) {
return (
<Container>
<Box sx={{ my: 4 }}>
<Box sx={{ my: STANDARD_MARGIN }}>
<Logo />
</Box>
<Box sx={{ my: 4 }}>
<Stack spacing={2}>
<Box sx={{ my: STANDARD_MARGIN }}>
<Stack spacing={STANDARD_SPACING}>
{props.children}
<Typography variant='body1'>
This is a web UI for running ONNX models with GPU acceleration or in software, running locally or on a

View File

@ -1,15 +1,14 @@
/* eslint-disable @typescript-eslint/no-magic-numbers */
import { mustExist } from '@apextoaster/js-utils';
import { TabContext, TabList, TabPanel } from '@mui/lab';
import { Box, Container, CssBaseline, Divider, Stack, Tab, useMediaQuery } from '@mui/material';
import { Breakpoint, SxProps, Theme, ThemeProvider, createTheme } from '@mui/material/styles';
import { SxProps, Theme, ThemeProvider, createTheme } from '@mui/material/styles';
import { Allotment } from 'allotment';
import * as React from 'react';
import { useContext, useMemo } from 'react';
import { useHash } from 'react-use/lib/useHash';
import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { LAYOUT_MIN, LAYOUT_PROPORTIONS, LAYOUT_STYLES, STANDARD_MARGIN, STANDARD_SPACING } from '../constants.js';
import { Motd } from '../Motd.js';
import { OnnxState, StateContext } from '../state/full.js';
import { Layout } from '../state/settings.js';
@ -36,7 +35,10 @@ export function OnnxWeb(props: OnnxWebProps) {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const store = mustExist(useContext(StateContext));
const stateTheme = useStore(store, selectTheme);
const layout = useStore(store, selectLayout, shallow);
const historyWidth = useStore(store, selectHistoryWidth);
const direction = useStore(store, selectDirection);
const layout = LAYOUT_STYLES[direction];
const theme = useMemo(
() => createTheme({
@ -47,17 +49,17 @@ export function OnnxWeb(props: OnnxWebProps) {
[prefersDarkMode, stateTheme],
);
const historyStyle: SxProps<Theme> = LAYOUT_STYLES[layout.direction].history.style;
const historyStyle: SxProps<Theme> = layout.history.style;
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Container maxWidth={LAYOUT_STYLES[layout.direction].container}>
<Box sx={{ my: 4 }}>
<Container maxWidth={layout.container}>
<Box sx={{ my: STANDARD_MARGIN }}>
<Logo />
</Box>
{props.motd && <Motd />}
{renderBody(layout, historyStyle)}
{renderBody(direction, historyStyle, historyWidth)}
</Container>
</ThemeProvider>
);
@ -67,52 +69,19 @@ export function selectTheme(state: OnnxState) {
return state.theme;
}
export function selectLayout(state: OnnxState) {
return {
direction: state.layout,
width: state.historyWidth,
};
export function selectDirection(state: OnnxState) {
return state.layout;
}
export const LAYOUT_STYLES = {
horizontal: {
container: false,
control: {
width: '30%',
},
direction: 'row',
divider: 'vertical',
history: {
style: {
marginLeft: 4,
maxHeight: '85vb',
overflowY: 'auto',
},
width: 4,
},
},
vertical: {
container: 'lg' as Breakpoint,
control: {
width: undefined,
},
direction: 'column',
divider: 'horizontal',
history: {
style: {
mx: 4,
my: 4,
},
width: 2,
},
},
} as const;
export function selectHistoryWidth(state: OnnxState) {
return state.historyWidth;
}
function renderBody(layout: ReturnType<typeof selectLayout>, historyStyle: SxProps<Theme>) {
if (layout.direction === 'vertical') {
return <VerticalBody {...layout} style={historyStyle} />;
function renderBody(direction: Layout, historyStyle: SxProps<Theme>, historyWidth: number) {
if (direction === 'vertical') {
return <VerticalBody direction={direction} style={historyStyle} width={historyWidth} />;
} else {
return <HorizontalBody {...layout} style={historyStyle} />;
return <HorizontalBody direction={direction} style={historyStyle} width={historyWidth} />;
}
}
@ -126,9 +95,9 @@ export interface BodyProps {
export function HorizontalBody(props: BodyProps) {
const layout = LAYOUT_STYLES[props.direction];
return <Allotment separator className='body-allotment' minSize={300}>
return <Allotment separator className='body-allotment' minSize={LAYOUT_MIN} defaultSizes={LAYOUT_PROPORTIONS} snap>
<TabGroup direction={props.direction} />
<Box sx={layout.history.style}>
<Box className='box-history' sx={layout.history.style}>
<ImageHistory width={props.width} />
</Box>
</Allotment>;
@ -137,10 +106,10 @@ export function HorizontalBody(props: BodyProps) {
export function VerticalBody(props: BodyProps) {
const layout = LAYOUT_STYLES[props.direction];
return <Stack direction={layout.direction} spacing={2}>
return <Stack direction={layout.direction} spacing={STANDARD_SPACING}>
<TabGroup direction={props.direction} />
<Divider flexItem variant='middle' orientation={layout.divider} />
<Box sx={layout.history.style}>
<Box className='box-history' sx={layout.history.style}>
<ImageHistory width={props.width} />
</Box>
</Stack>;
@ -155,7 +124,7 @@ export function TabGroup(props: TabGroupProps) {
const [hash, setHash] = useHash();
return <Stack direction='column' minWidth={layout.control.width} sx={{ mx: 4 }}>
return <Stack direction='column' minWidth={layout.control.width} sx={{ mx: STANDARD_MARGIN }}>
<TabContext value={getTab(hash)}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<TabList onChange={(_e, idx) => {

View File

@ -21,12 +21,15 @@ import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { STANDARD_SPACING } from '../constants.js';
import { OnnxState, StateContext } from '../state/full.js';
import { ImageMetadata } from '../types/api.js';
import { AnyImageMetadata } from '../types/api-v2.js';
import { DeepPartial } from '../types/model.js';
import { BaseImgParams, HighresParams, ModelParams, Txt2ImgParams, UpscaleParams } from '../types/params.js';
import { downloadAsJson } from '../utils.js';
export type PartialImageMetadata = DeepPartial<AnyImageMetadata>;
export const ALLOWED_EXTENSIONS = ['.json','.jpg','.jpeg','.png','.txt','.webp'];
export const EXTENSION_FILTER = ALLOWED_EXTENSIONS.join(',');
@ -51,7 +54,7 @@ export function Profiles(props: ProfilesProps) {
const [profileName, setProfileName] = useState('');
const { t } = useTranslation();
return <Stack direction='row' spacing={2}>
return <Stack direction='row' spacing={STANDARD_SPACING}>
<Autocomplete
id='profile-select'
options={profiles}
@ -193,7 +196,7 @@ export function selectProfiles(state: OnnxState) {
return state.profiles;
}
export async function loadParamsFromFile(file: File): Promise<DeepPartial<ImageMetadata>> {
export async function loadParamsFromFile(file: File): Promise<PartialImageMetadata> {
const parts = file.name.toLocaleLowerCase().split('.');
const ext = parts[parts.length - 1];
@ -211,7 +214,7 @@ export async function loadParamsFromFile(file: File): Promise<DeepPartial<ImageM
}
}
export async function parseImageParams(file: File): Promise<DeepPartial<ImageMetadata>> {
export async function parseImageParams(file: File): Promise<PartialImageMetadata> {
const tags = await ExifReader.load(file);
// some parsers expect uppercase, some use lowercase, read both
@ -251,8 +254,8 @@ export function decodeTag(tag: Maybe<ExifReader.XmpTag | (ExifReader.NumberTag &
throw new InvalidArgumentError('tag value cannot be decoded');
}
export async function parseJSONParams(json: string): Promise<DeepPartial<ImageMetadata>> {
const data = JSON.parse(json) as DeepPartial<ImageMetadata>;
export async function parseJSONParams(json: string): Promise<PartialImageMetadata> {
const data = JSON.parse(json) as PartialImageMetadata;
const params: Partial<Txt2ImgParams> = {
...data.params,
};
@ -276,7 +279,7 @@ export function isProbablyJSON(maybeJSON: unknown): boolean {
export const NEGATIVE_PROMPT_TAG = 'Negative prompt:';
export async function parseAutoComment(comment: string): Promise<DeepPartial<ImageMetadata>> {
export async function parseAutoComment(comment: string): Promise<PartialImageMetadata> {
if (isProbablyJSON(comment)) {
return parseJSONParams(comment);
}

View File

@ -11,6 +11,7 @@ import { shallow } from 'zustand/shallow';
import { ClientContext, ConfigContext, OnnxState, StateContext } from '../../state/full.js';
import { FailedJobResponse, JobStatus, RetryParams, UnknownJobResponse } from '../../types/api-v2.js';
import { STANDARD_SPACING } from '../../constants.js';
export interface ErrorCardProps {
image: FailedJobResponse | UnknownJobResponse;
@ -48,7 +49,7 @@ export function ErrorCard(props: ErrorCardProps) {
}}>
<Stack
direction='column'
spacing={2}
spacing={STANDARD_SPACING}
sx={{ alignItems: 'center' }}
>
<Alert severity='error'>
@ -56,7 +57,7 @@ export function ErrorCard(props: ErrorCardProps) {
<br />
{t(getImageErrorReason(image))}
</Alert>
<Stack direction='row' spacing={2}>
<Stack direction='row' spacing={STANDARD_SPACING}>
<Tooltip title={t('tooltip.retry')}>
<IconButton onClick={() => retry.mutate()}>
<Replay />

View File

@ -10,7 +10,7 @@ import { shallow } from 'zustand/shallow';
import { ClientContext, ConfigContext, OnnxState, StateContext } from '../../state/full.js';
import { range, visibleIndex } from '../../utils.js';
import { BLEND_SOURCES } from '../../constants.js';
import { BLEND_SOURCES, STANDARD_SPACING } from '../../constants.js';
import { JobResponse, SuccessJobResponse } from '../../types/api-v2.js';
export interface ImageCardProps {
@ -120,7 +120,7 @@ export function ImageCard(props: ImageCardProps) {
/>
<CardContent>
<Box textAlign='center'>
<Grid container spacing={2}>
<Grid container spacing={STANDARD_SPACING}>
<GridItem xs={4}>
<Tooltip title={t('tooltip.previous')}>
<IconButton onClick={() => {

View File

@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { POLL_TIME } from '../../config.js';
import { POLL_TIME, STANDARD_SPACING } from '../../constants.js';
import { ClientContext, ConfigContext, OnnxState, StateContext } from '../../state/full.js';
import { JobResponse, JobStatus } from '../../types/api-v2.js';
import { visibleIndex } from '../../utils.js';
@ -71,7 +71,7 @@ export function LoadingCard(props: LoadingCardProps) {
}}>
<Stack
direction='column'
spacing={2}
spacing={STANDARD_SPACING}
sx={{ alignItems: 'center' }}
>
{renderProgress()}

View File

@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { STALE_TIME } from '../../config.js';
import { STALE_TIME, STANDARD_SPACING } from '../../constants.js';
import { ClientContext, ConfigContext, OnnxState, StateContext } from '../../state/full.js';
import { BaseImgParams } from '../../types/params.js';
import { NumericField } from '../input/NumericField.js';
@ -46,7 +46,7 @@ export function ImageControl(props: ImageControlProps) {
staleTime: STALE_TIME,
});
return <Stack spacing={2}>
return <Stack spacing={STANDARD_SPACING}>
<Stack direction='row' spacing={4}>
<QueryList
id='schedulers'

View File

@ -5,11 +5,11 @@ import * as React from 'react';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { STALE_TIME } from '../../config.js';
import { STALE_TIME, STANDARD_SPACING } from '../../constants.js';
import { ClientContext } from '../../state/full.js';
import { JobType } from '../../types/api-v2.js';
import { ModelParams } from '../../types/params.js';
import { QueryList } from '../input/QueryList.js';
import { JobType } from '../../types/api-v2.js';
export interface ModelControlProps {
model: ModelParams;
@ -35,7 +35,7 @@ export function ModelControl(props: ModelControlProps) {
staleTime: STALE_TIME,
});
return <Stack direction='row' spacing={2}>
return <Stack direction='row' spacing={STANDARD_SPACING}>
<QueryList
id='platforms'
labelKey='platform'

View File

@ -7,6 +7,7 @@ import { useStore } from 'zustand';
import { PipelineGrid } from '../../client/utils.js';
import { OnnxState, StateContext } from '../../state/full.js';
import { VARIABLE_PARAMETERS } from '../../types/chain.js';
import { STANDARD_SPACING } from '../../constants.js';
export interface VariableControlProps {
selectGrid: (state: OnnxState) => PipelineGrid;
@ -20,7 +21,7 @@ export function VariableControl(props: VariableControlProps) {
const grid = useStore(store, props.selectGrid);
const stack = [
<Stack direction='row' spacing={2} key='variable-enable'>
<Stack direction='row' spacing={STANDARD_SPACING} key='variable-enable'>
<FormControl>
<FormControlLabel
label='Grid Mode'
@ -37,7 +38,7 @@ export function VariableControl(props: VariableControlProps) {
if (grid.enabled) {
stack.push(
<Stack direction='row' spacing={2} key='variable-row'>
<Stack direction='row' spacing={STANDARD_SPACING} key='variable-row'>
<FormControl>
<InputLabel id='TODO'>Columns</InputLabel>
<Select onChange={(event) => props.setGrid({
@ -56,7 +57,7 @@ export function VariableControl(props: VariableControlProps) {
},
})} />
</Stack>,
<Stack direction='row' spacing={2} key='variable-column'>
<Stack direction='row' spacing={STANDARD_SPACING} key='variable-column'>
<FormControl>
<InputLabel id='TODO'>Rows</InputLabel>
<Select onChange={(event) => props.setGrid({
@ -78,7 +79,7 @@ export function VariableControl(props: VariableControlProps) {
);
}
return <Stack direction='column' spacing={2}>{...stack}</Stack>;
return <Stack direction='column' spacing={STANDARD_SPACING}>{...stack}</Stack>;
}
export function parameterList(exclude?: Array<string>) {

View File

@ -1,7 +1,7 @@
import { Alert, AlertTitle, Typography } from '@mui/material';
import * as React from 'react';
import { PARAM_VERSION } from '../../config.js';
import { PARAM_VERSION } from '../../constants';
export interface ParamsVersionErrorProps {
root: string;

View File

@ -5,6 +5,7 @@ import { memo, useContext, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { STANDARD_SPACING } from '../../constants.js';
import { OnnxState, StateContext } from '../../state/full.js';
export interface EditableListProps<T> {
@ -30,7 +31,7 @@ export function EditableList<T>(props: EditableListProps<T>) {
const [nextSource, setNextSource] = useState('');
const RenderMemo = useMemo(() => memo(renderItem), [renderItem]);
return <Stack spacing={2}>
return <Stack spacing={STANDARD_SPACING}>
{items.map((model, idx) =>
<RenderMemo
key={idx}
@ -39,7 +40,7 @@ export function EditableList<T>(props: EditableListProps<T>) {
onRemove={removeItem}
/>
)}
<Stack direction='row' spacing={2}>
<Stack direction='row' spacing={STANDARD_SPACING}>
<TextField
label={t('extras.label')}
variant='outlined'

View File

@ -4,6 +4,8 @@ import { Button, Stack, Typography } from '@mui/material';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { STANDARD_SPACING } from '../../constants';
export interface ImageInputProps {
filter: string;
image?: Maybe<Blob>;
@ -35,7 +37,7 @@ export function ImageInput(props: ImageInputProps) {
}
}
return <Stack direction='row' spacing={2}>
return <Stack direction='row' spacing={STANDARD_SPACING}>
<Stack>
<Button component='label' startIcon={<PhotoCamera />} variant='outlined'>
{props.label}

View File

@ -5,7 +5,7 @@ import { throttle } from 'lodash';
import React, { RefObject, useContext, useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { SAVE_TIME } from '../../config.js';
import { SAVE_TIME, STANDARD_SPACING } from '../../constants.js';
import { ConfigContext, LoggerContext, StateContext } from '../../state/full.js';
import { BrushParams } from '../../types/params.js';
import { imageFromBlob } from '../../utils.js';
@ -255,8 +255,8 @@ export function MaskCanvas(props: MaskCanvasProps) {
display: 'none',
};
return <Stack spacing={2}>
<Stack direction='row' spacing={2}>
return <Stack spacing={STANDARD_SPACING}>
<Stack direction='row' spacing={STANDARD_SPACING}>
<Button
variant='outlined'
startIcon={<Download />}
@ -348,7 +348,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
}}
/>
</Stack>
<Stack direction='row' spacing={2}>
<Stack direction='row' spacing={STANDARD_SPACING}>
<Button
variant='outlined'
startIcon={<FormatColorFill />}

View File

@ -3,6 +3,8 @@ import { Slider, Stack, TextField } from '@mui/material';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { STANDARD_SPACING } from '../../constants';
export function parseNumber(num: string, decimal = false): number {
if (decimal) {
return parseFloat(num);
@ -29,7 +31,7 @@ export function NumericField(props: ImageControlProps) {
const { t } = useTranslation();
return <Stack spacing={2}>
return <Stack spacing={STANDARD_SPACING}>
<TextField
error={error}
label={label}

View File

@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { STALE_TIME } from '../../config.js';
import { STALE_TIME, STANDARD_SPACING } from '../../constants.js';
import { ClientContext, OnnxState, StateContext } from '../../state/full.js';
import { ModelResponse, NetworkModel } from '../../types/api.js';
import { QueryMenu, QueryMenuComplete, QueryMenuFilter } from '../input/QueryMenu.js';
@ -55,7 +55,7 @@ export function PromptTextBlock(props: PromptTextBlockProps) {
return [];
}, [models, prompt]);
return <Stack spacing={2}>
return <Stack spacing={STANDARD_SPACING}>
<TextField
label={t('parameter.prompt')}
variant='outlined'
@ -67,7 +67,7 @@ export function PromptTextBlock(props: PromptTextBlockProps) {
});
}}
/>
<Stack direction='row' spacing={2}>
<Stack direction='row' spacing={STANDARD_SPACING}>
{tokens.map((token) => <Chip
color={prompt.includes(token) ? 'primary' : 'default'}
label={token}
@ -139,13 +139,13 @@ export function PromptInput(props: PromptInputProps) {
result: wildcards,
}), [wildcards.status]);
return <Stack spacing={2}>
return <Stack spacing={STANDARD_SPACING}>
<PromptTextBlock
models={models.data}
onChange={onChange}
selector={selector}
/>
<Stack direction='row' spacing={2}>
<Stack direction='row' spacing={STANDARD_SPACING}>
<ModelMenu
id='inversion'
labelKey='model.inversion'

View File

@ -2,6 +2,7 @@ import { Button, MenuItem, Select, Stack, TextField } from '@mui/material';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { STANDARD_SPACING } from '../../../constants.js';
import { CorrectionArch, CorrectionModel, ModelFormat } from '../../../types/model.js';
export interface CorrectionModelInputProps {
@ -16,7 +17,7 @@ export function CorrectionModelInput(props: CorrectionModelInputProps) {
const { key, model, onChange, onRemove } = props;
const { t } = useTranslation();
return <Stack direction='row' spacing={2} key={key}>
return <Stack direction='row' spacing={STANDARD_SPACING} key={key}>
<TextField
label={t('extras.label')}
value={model.label}

View File

@ -2,6 +2,7 @@ import { Button, MenuItem, Select, Stack, TextField } from '@mui/material';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { STANDARD_SPACING } from '../../../constants.js';
import { DiffusionModel, ModelFormat } from '../../../types/model.js';
export interface DiffusionModelInputProps {
@ -16,7 +17,7 @@ export function DiffusionModelInput(props: DiffusionModelInputProps) {
const { key, model, onChange, onRemove } = props;
const { t } = useTranslation();
return <Stack direction='row' spacing={2} key={key}>
return <Stack direction='row' spacing={STANDARD_SPACING} key={key}>
<TextField
label={t('extras.label')}
value={model.label}

View File

@ -2,6 +2,7 @@ import { Button, MenuItem, Select, Stack, TextField } from '@mui/material';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { STANDARD_SPACING } from '../../../constants.js';
import { ExtraNetwork, ModelFormat, NetworkModel, NetworkType } from '../../../types/model.js';
export interface ExtraNetworkInputProps {
@ -16,7 +17,7 @@ export function ExtraNetworkInput(props: ExtraNetworkInputProps) {
const { key, model, onChange, onRemove } = props;
const { t } = useTranslation();
return <Stack direction='row' spacing={2} key={key}>
return <Stack direction='row' spacing={STANDARD_SPACING} key={key}>
<TextField
label={t('extras.label')}
value={model.label}

View File

@ -2,6 +2,7 @@ import { Button, MenuItem, Select, Stack, TextField } from '@mui/material';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { STANDARD_SPACING } from '../../../constants.js';
import { AnyFormat, ExtraSource } from '../../../types/model.js';
export interface ExtraSourceInputProps {
@ -16,7 +17,7 @@ export function ExtraSourceInput(props: ExtraSourceInputProps) {
const { key, model, onChange, onRemove } = props;
const { t } = useTranslation();
return <Stack direction='row' spacing={2} key={key}>
return <Stack direction='row' spacing={STANDARD_SPACING} key={key}>
<TextField
label={t('extras.name')}
value={model.name}

View File

@ -2,6 +2,7 @@ import { Button, MenuItem, Select, Stack, TextField } from '@mui/material';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { STANDARD_SPACING } from '../../../constants.js';
import { ModelFormat, UpscalingArch, UpscalingModel } from '../../../types/model.js';
import { NumericField } from '../NumericField.js';
@ -17,7 +18,7 @@ export function UpscalingModelInput(props: UpscalingModelInputProps) {
const { key, model, onChange, onRemove } = props;
const { t } = useTranslation();
return <Stack direction='row' spacing={2} key={key}>
return <Stack direction='row' spacing={STANDARD_SPACING} key={key}>
<TextField
label={t('extras.label')}
value={model.label}

View File

@ -1,3 +1,8 @@
.body-allotment {
height: 85vb;
height: 90vb;
}
.box-history {
max-height: 90vh;
overflow-y: auto;
}

View File

@ -7,8 +7,7 @@ import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { IMAGE_FILTER } from '../../config.js';
import { BLEND_SOURCES } from '../../constants.js';
import { BLEND_SOURCES, IMAGE_FILTER, STANDARD_SPACING } from '../../constants.js';
import { ClientContext, OnnxState, StateContext } from '../../state/full.js';
import { TabState } from '../../state/types.js';
import { BlendParams, BrushParams, ModelParams, UpscaleParams } from '../../types/params.js';
@ -44,7 +43,7 @@ export function Blend() {
const sources = mustDefault(blend.sources, []);
return <Box>
<Stack spacing={2}>
<Stack spacing={STANDARD_SPACING}>
{range(BLEND_SOURCES).map((idx) =>
<ImageInput
key={`source-${idx.toFixed(0)}`}

View File

@ -7,9 +7,10 @@ import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { IMAGE_FILTER, STALE_TIME } from '../../config.js';
import { IMAGE_FILTER, STALE_TIME, STANDARD_SPACING } from '../../constants.js';
import { ClientContext, ConfigContext, OnnxState, StateContext } from '../../state/full.js';
import { TabState } from '../../state/types.js';
import { JobType } from '../../types/api-v2.js';
import { HighresParams, Img2ImgParams, ModelParams, UpscaleParams } from '../../types/params.js';
import { Profiles } from '../Profiles.js';
import { HighresControl } from '../control/HighresControl.js';
@ -19,7 +20,6 @@ import { UpscaleControl } from '../control/UpscaleControl.js';
import { ImageInput } from '../input/ImageInput.js';
import { NumericField } from '../input/NumericField.js';
import { QueryList } from '../input/QueryList.js';
import { JobType } from '../../types/api-v2.js';
export function Img2Img() {
const { params } = mustExist(useContext(ConfigContext));
@ -57,7 +57,7 @@ export function Img2Img() {
const { t } = useTranslation();
return <Box>
<Stack spacing={2}>
<Stack spacing={STANDARD_SPACING}>
<Profiles
selectHighres={selectHighres}
selectModel={selectModel}
@ -80,7 +80,7 @@ export function Img2Img() {
}}
/>
<ImageControl selector={selectParams} onChange={setImg2Img} />
<Stack direction='row' spacing={2}>
<Stack direction='row' spacing={STANDARD_SPACING}>
<QueryList
id='control'
labelKey='model.control'

View File

@ -7,9 +7,10 @@ import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { IMAGE_FILTER, STALE_TIME } from '../../config.js';
import { IMAGE_FILTER, STALE_TIME, STANDARD_SPACING } from '../../constants.js';
import { ClientContext, ConfigContext, OnnxState, StateContext } from '../../state/full.js';
import { TabState } from '../../state/types.js';
import { JobType } from '../../types/api-v2.js';
import { BrushParams, HighresParams, InpaintParams, ModelParams, UpscaleParams } from '../../types/params.js';
import { Profiles } from '../Profiles.js';
import { HighresControl } from '../control/HighresControl.js';
@ -21,7 +22,6 @@ import { ImageInput } from '../input/ImageInput.js';
import { MaskCanvas } from '../input/MaskCanvas.js';
import { NumericField } from '../input/NumericField.js';
import { QueryList } from '../input/QueryList.js';
import { JobType } from '../../types/api-v2.js';
export function Inpaint() {
const { params } = mustExist(useContext(ConfigContext));
@ -89,7 +89,7 @@ export function Inpaint() {
}
return <Box>
<Stack spacing={2}>
<Stack spacing={STANDARD_SPACING}>
<Profiles
selectHighres={selectHighres}
selectModel={selectModel}
@ -153,7 +153,7 @@ export function Inpaint() {
});
}}
/>
<Stack direction='row' spacing={2}>
<Stack direction='row' spacing={STANDARD_SPACING}>
<QueryList
id='masks'
labelKey={'maskFilter'}
@ -200,7 +200,7 @@ export function Inpaint() {
}
</Select>
</FormControl>
<Stack direction='row' spacing={2}>
<Stack direction='row' spacing={STANDARD_SPACING}>
<FormControlLabel
label={t('parameter.fillColor')}
sx={{ mx: 1 }}

View File

@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { STALE_TIME } from '../../config.js';
import { STALE_TIME, STANDARD_SPACING } from '../../constants.js';
import { ClientContext, OnnxState, StateContext } from '../../state/full.js';
import {
CorrectionModel,
@ -111,13 +111,13 @@ export function Models() {
}, [result.status]);
if (result.status === 'error') {
return <Stack spacing={2} direction='row' sx={{ alignItems: 'center' }}>
return <Stack spacing={STANDARD_SPACING} direction='row' sx={{ alignItems: 'center' }}>
<Alert severity='error'>Error</Alert>
</Stack>;
}
if (result.status === 'loading') {
return <Stack spacing={2} direction='row' sx={{ alignItems: 'center' }}>
return <Stack spacing={STANDARD_SPACING} direction='row' sx={{ alignItems: 'center' }}>
<CircularProgress />
</Stack>;
}
@ -127,7 +127,7 @@ export function Models() {
// TODO: do something with resp
}
return <Stack spacing={2}>
return <Stack spacing={STANDARD_SPACING}>
<Accordion>
<AccordionSummary>
{t('modelType.diffusion', { count: 10 })}

View File

@ -11,6 +11,7 @@ import { ConfigContext, StateContext, STATE_KEY } from '../../state/full.js';
import { getTheme } from '../utils.js';
import { NumericField } from '../input/NumericField.js';
import { downloadAsJson } from '../../utils.js';
import { STANDARD_SPACING } from '../../constants.js';
function removeBlobs(key: string, value: unknown): unknown {
if (value instanceof Blob || value instanceof File) {
@ -39,7 +40,7 @@ export function Settings() {
const [root, setRoot] = useState(getApiRoot(config));
const { t } = useTranslation();
return <Stack spacing={2}>
return <Stack spacing={STANDARD_SPACING}>
<NumericField
label={t('setting.history.limit')}
min={2}
@ -67,7 +68,7 @@ export function Settings() {
scheduler: event.target.value,
});
}} />
<Stack direction='row' spacing={2}>
<Stack direction='row' spacing={STANDARD_SPACING}>
<TextField variant='outlined' label={t('setting.server')} value={root} onChange={(event) => {
setRoot(event.target.value);
}} />
@ -82,7 +83,7 @@ export function Settings() {
{config.params.version}
</Alert>
</Stack>
<Stack direction='row' spacing={2}>
<Stack direction='row' spacing={STANDARD_SPACING}>
<TextField variant='outlined' label={t('setting.state.label')} value={json} onChange={(event) => {
setJson(event.target.value);
}} />
@ -109,7 +110,7 @@ export function Settings() {
}}
/>
} label={t('setting.darkMode')} />
<Stack direction='row' spacing={2}>
<Stack direction='row' spacing={STANDARD_SPACING}>
<Button onClick={() => state.resetTxt2Img()} color='warning'>{t('setting.reset.txt2img')}</Button>
<Button onClick={() => state.resetImg2Img()} color='warning'>{t('setting.reset.img2img')}</Button>
<Button onClick={() => state.resetInpaint()} color='warning'>{t('setting.reset.inpaint')}</Button>

View File

@ -8,8 +8,10 @@ import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { PipelineGrid, makeTxt2ImgGridPipeline } from '../../client/utils.js';
import { STANDARD_SPACING } from '../../constants.js';
import { ClientContext, ConfigContext, OnnxState, StateContext } from '../../state/full.js';
import { TabState } from '../../state/types.js';
import { JobType } from '../../types/api-v2.js';
import { HighresParams, ModelParams, Txt2ImgParams, UpscaleParams } from '../../types/params.js';
import { Profiles } from '../Profiles.js';
import { HighresControl } from '../control/HighresControl.js';
@ -18,7 +20,6 @@ import { ModelControl } from '../control/ModelControl.js';
import { UpscaleControl } from '../control/UpscaleControl.js';
import { VariableControl } from '../control/VariableControl.js';
import { NumericField } from '../input/NumericField.js';
import { JobType } from '../../types/api-v2.js';
export function SizeControl() {
const { params } = mustExist(useContext(ConfigContext));
@ -88,7 +89,7 @@ export function Txt2Img() {
const { t } = useTranslation();
return <Box>
<Stack spacing={2}>
<Stack spacing={STANDARD_SPACING}>
<Profiles
selectHighres={selectHighres}
selectModel={selectModel}

View File

@ -7,9 +7,10 @@ import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { shallow } from 'zustand/shallow';
import { IMAGE_FILTER } from '../../config.js';
import { IMAGE_FILTER, STANDARD_SPACING } from '../../constants.js';
import { ClientContext, OnnxState, StateContext } from '../../state/full.js';
import { TabState } from '../../state/types.js';
import { JobType } from '../../types/api-v2.js';
import { HighresParams, ModelParams, UpscaleParams, UpscaleReqParams } from '../../types/params.js';
import { Profiles } from '../Profiles.js';
import { HighresControl } from '../control/HighresControl.js';
@ -17,7 +18,6 @@ import { ModelControl } from '../control/ModelControl.js';
import { UpscaleControl } from '../control/UpscaleControl.js';
import { ImageInput } from '../input/ImageInput.js';
import { PromptInput } from '../input/PromptInput.js';
import { JobType } from '../../types/api-v2.js';
export function Upscale() {
async function uploadSource() {
@ -43,7 +43,7 @@ export function Upscale() {
const { t } = useTranslation();
return <Box>
<Stack spacing={2}>
<Stack spacing={STANDARD_SPACING}>
<Profiles
selectHighres={selectHighres}
selectModel={selectModel}

View File

@ -1,7 +1,6 @@
import { doesExist, Maybe } from '@apextoaster/js-utils';
import { merge } from 'lodash';
import { STATUS_SUCCESS } from './client/api.js';
import {
HighresParams,
Img2ImgParams,
@ -11,6 +10,7 @@ import {
Txt2ImgParams,
UpscaleParams,
} from './types/params.js';
import { STATUS_SUCCESS } from './constants.js';
export interface ConfigBoolean {
default: boolean;
@ -93,13 +93,6 @@ export interface Config<T = ClientParams> {
params: T;
}
export const IMAGE_FILTER = '.bmp, .jpg, .jpeg, .png';
export const PARAM_VERSION = '>=0.10.0';
export const STALE_TIME = 300_000; // 5 minutes
export const POLL_TIME = 5_000; // 5 seconds
export const SAVE_TIME = 5_000; // 5 seconds
export async function loadConfig(): Promise<Config> {
const configPath = new URL('./config.json', window.location.href);
const configReq = await fetch(configPath);

View File

@ -1,3 +1,4 @@
import { Breakpoint } from '@mui/material/styles';
export const BLEND_SOURCES = 2;
@ -29,3 +30,66 @@ export const DEFAULT_HISTORY = {
*/
scrollback: 4,
};
export const STANDARD_MARGIN = 4; // translated into 32px by mui
export const STANDARD_SPACING = 2;
export const LAYOUT_MIN = 300;
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
export const LAYOUT_PROPORTIONS = [100, 200];
export const LAYOUT_STYLES = {
horizontal: {
container: false,
control: {
width: '30%',
},
direction: 'row',
divider: 'vertical',
history: {
style: {
ml: STANDARD_MARGIN,
},
width: 4,
},
},
vertical: {
container: 'lg' as Breakpoint,
control: {
width: undefined,
},
direction: 'column',
divider: 'horizontal',
history: {
style: {
mx: STANDARD_MARGIN,
my: STANDARD_MARGIN,
},
width: 2,
},
},
} as const;
export const INITIAL_LOAD_TIMEOUT = 5_000;
export const STALE_TIME = 300_000; // 5 minutes
export const POLL_TIME = 5_000; // 5 seconds
export const SAVE_TIME = 5_000; // 5 seconds
export const IMAGE_FILTER = '.bmp, .jpg, .jpeg, .png';
export const PARAM_VERSION = '>=0.10.0';
/**
* Fixed precision for integer parameters.
*/
export const FIXED_INTEGER = 0;
/**
* Fixed precision for float parameters.
*
* The GUI limits the input steps based on the server parameters, but this does limit
* the maximum precision that can be sent back to the server, and may have to be
* increased in the future.
*/
export const FIXED_FLOAT = 2;
export const STATUS_SUCCESS = 200;

View File

@ -18,7 +18,7 @@ import { ServerParamsError } from './components/error/ServerParams.js';
import { LoadingScreen } from './components/LoadingScreen.js';
import { OnnxError } from './components/OnnxError.js';
import { OnnxWeb } from './components/OnnxWeb.js';
import { Config, getApiRoot, isDebug, loadConfig, mergeConfig, PARAM_VERSION, ServerParams } from './config.js';
import { Config, getApiRoot, isDebug, loadConfig, mergeConfig, ServerParams } from './config.js';
import {
ClientContext,
ConfigContext,
@ -31,8 +31,7 @@ import {
} from './state/full.js';
import { I18N_STRINGS } from './strings/all.js';
import { applyStateMigrations, UnknownState } from './state/migration/default.js';
export const INITIAL_LOAD_TIMEOUT = 5_000;
import { INITIAL_LOAD_TIMEOUT, PARAM_VERSION } from './constants.js';
export async function renderApp(config: Config, params: ServerParams, logger: Logger, client: ApiClient) {
const completeConfig = mergeConfig(config, params);

View File

@ -1,15 +1,9 @@
import { Maybe } from '@apextoaster/js-utils';
import { ImageResponse, ReadyResponse, RetryParams } from '../types/api.js';
import { RetryParams } from '../types/api.js';
import { Slice } from './types.js';
import { DEFAULT_HISTORY } from '../constants.js';
import { JobResponse } from '../types/api-v2.js';
export interface HistoryItem {
image: ImageResponse;
ready: Maybe<ReadyResponse>;
retry: Maybe<RetryParams>;
}
export interface HistoryItemV2 {
image: JobResponse;
retry: Maybe<RetryParams>;

View File

@ -34,6 +34,13 @@ export interface ImageMetadata<TParams extends BaseImgParams, TType extends JobT
type: TType;
}
export type AnyImageMetadata
= ImageMetadata<Txt2ImgParams, JobType.TXT2IMG>
| ImageMetadata<Img2ImgParams, JobType.IMG2IMG>
| ImageMetadata<InpaintParams, JobType.INPAINT>
| ImageMetadata<BaseImgParams, JobType.UPSCALE>
| ImageMetadata<BaseImgParams, JobType.BLEND>;
export enum JobStatus {
PENDING = 'pending',
RUNNING = 'running',
@ -143,13 +150,7 @@ export interface SuccessBlendJobResponse extends BaseJobResponse {
export interface SuccessChainJobResponse extends BaseJobResponse {
status: JobStatus.SUCCESS;
outputs: Array<string>;
metadata: Array<
ImageMetadata<Txt2ImgParams, JobType.TXT2IMG>
| ImageMetadata<Img2ImgParams, JobType.IMG2IMG>
| ImageMetadata<InpaintParams, JobType.INPAINT>
| ImageMetadata<BaseImgParams, JobType.UPSCALE>
| ImageMetadata<BaseImgParams, JobType.BLEND>
>;
metadata: Array<AnyImageMetadata>;
}
/**

View File

@ -1,38 +1,15 @@
import {
BaseImgParams,
ModelParams,
Txt2ImgParams,
UpscaleParams,
BlendParams,
HighresParams,
Img2ImgParams,
InpaintParams,
ModelParams,
OutpaintParams,
Txt2ImgParams,
UpscaleParams,
UpscaleReqParams,
BlendParams,
ImageSize,
} from './params.js';
/**
* Output image data within the response.
*
* @deprecated
*/
export interface ImageOutput {
key: string;
url: string;
}
/**
* General response for most image requests.
*
* @deprecated
*/
export interface ImageResponse {
outputs: Array<ImageOutput>;
params: Required<BaseImgParams> & Required<ModelParams>;
size: ImageSize;
}
/**
* Status response from the ready endpoint.
*/
@ -122,26 +99,3 @@ export type RetryParams = {
params: BlendParams;
upscale?: UpscaleParams;
};
/**
* Status response from the image endpoint, with parameters to retry the job if it fails.
*
* @deprecated
*/
export interface ImageResponseWithRetry {
image: ImageResponse;
retry: RetryParams;
}
/**
* @deprecated
*/
export interface ImageMetadata {
highres: HighresParams;
outputs: string | Array<string>;
params: Txt2ImgParams | Img2ImgParams | InpaintParams;
upscale: UpscaleParams;
input_size: ImageSize;
size: ImageSize;
}