1
0
Fork 0

fix(gui): load remaining defaults from server params

This commit is contained in:
Sean Sube 2023-01-27 17:27:11 -06:00
parent 9b37c174d1
commit 88e4f74efe
4 changed files with 352 additions and 113 deletions

View File

@ -1,5 +1,11 @@
{
"version": "0.5.0",
"bottom": {
"default": 0,
"min": 0,
"max": 512,
"step": 8
},
"cfg": {
"default": 6,
"min": 1,
@ -18,12 +24,26 @@
"max": 1,
"step": 0.1
},
"fillColor": {
"default": "#000000",
"keys": []
},
"filter": {
"default": "none",
"keys": []
},
"height": {
"default": 512,
"min": 64,
"max": 1024,
"step": 8
},
"left": {
"default": 0,
"min": 0,
"max": 512,
"step": 8
},
"model": {
"default": "stable-diffusion-onnx-v1-5",
"keys": []
@ -32,6 +52,10 @@
"default": "",
"keys": []
},
"noise": {
"default": "histogram",
"keys": []
},
"outscale": {
"default": 1,
"min": 1,
@ -46,6 +70,12 @@
"default": "an astronaut eating a hamburger",
"keys": []
},
"right": {
"default": 0,
"min": 0,
"max": 512,
"step": 8
},
"scale": {
"default": 1,
"min": 1,
@ -74,6 +104,12 @@
"max": 1,
"step": 0.01
},
"top": {
"default": 0,
"min": 0,
"max": 512,
"step": 8
},
"width": {
"default": 512,
"min": 64,

View File

@ -1,22 +1,36 @@
/* eslint-disable max-lines */
import { doesExist } from '@apextoaster/js-utils';
import { ServerParams } from './config.js';
/**
* Shared parameters for anything using models, which is pretty much everything.
*/
export interface ModelParams {
/**
* Which ONNX model to use.
* The diffusion model to use.
*/
model: string;
/**
* Hardware accelerator or CPU mode.
* The hardware acceleration platform to use.
*/
platform: string;
/**
* The upscaling model to use.
*/
upscaling: string;
/**
* The correction model to use.
*/
correction: string;
}
/**
* Shared parameters for most of the image requests.
*/
export interface BaseImgParams {
scheduler: string;
prompt: string;
@ -27,20 +41,25 @@ export interface BaseImgParams {
seed: number;
}
export interface Img2ImgParams extends BaseImgParams {
source: Blob;
strength: number;
}
export type Img2ImgResponse = Required<Omit<Img2ImgParams, 'file'>>;
/**
* Parameters for txt2img requests.
*/
export interface Txt2ImgParams extends BaseImgParams {
width?: number;
height?: number;
}
export type Txt2ImgResponse = Required<Txt2ImgParams>;
/**
* Parameters for img2img requests.
*/
export interface Img2ImgParams extends BaseImgParams {
source: Blob;
strength: number;
}
/**
* Parameters for inpaint requests.
*/
export interface InpaintParams extends BaseImgParams {
mask: Blob;
source: Blob;
@ -51,6 +70,11 @@ export interface InpaintParams extends BaseImgParams {
fillColor: string;
}
/**
* Additional parameters for outpaint border.
*
* @todo should be nested under inpaint/outpaint params
*/
export interface OutpaintPixels {
enabled: boolean;
@ -60,14 +84,27 @@ export interface OutpaintPixels {
bottom: number;
}
/**
* Parameters for outpaint requests.
*/
export type OutpaintParams = InpaintParams & OutpaintPixels;
/**
* Additional parameters for the inpaint brush.
*
* These are not currently sent to the server and only stored in state.
*
* @todo move to state
*/
export interface BrushParams {
color: number;
size: number;
strength: number;
}
/**
* Additional parameters for upscaling.
*/
export interface UpscaleParams {
enabled: boolean;
@ -78,10 +115,16 @@ export interface UpscaleParams {
faceStrength: number;
}
/**
* Parameters for upscale requests.
*/
export interface UpscaleReqParams {
source: Blob;
}
/**
* General response for most image requests.
*/
export interface ImageResponse {
output: {
key: string;
@ -94,10 +137,16 @@ export interface ImageResponse {
};
}
/**
* Status response from the ready endpoint.
*/
export interface ReadyResponse {
ready: boolean;
}
/**
* List of available models.
*/
export interface ModelsResponse {
diffusion: Array<string>;
correction: Array<string>;
@ -105,50 +154,103 @@ export interface ModelsResponse {
}
export interface ApiClient {
/**
* List the available filter masks for inpaint.
*/
masks(): Promise<Array<string>>;
/**
* List the available models.
*/
models(): Promise<ModelsResponse>;
/**
* List the available noise sources for inpaint.
*/
noises(): Promise<Array<string>>;
/**
* Get the valid server parameters to validate image parameters.
*/
params(): Promise<ServerParams>;
/**
* Get the available hardware acceleration platforms.
*/
platforms(): Promise<Array<string>>;
/**
* List the available pipeline schedulers.
*/
schedulers(): Promise<Array<string>>;
img2img(model: ModelParams, params: Img2ImgParams, upscale?: UpscaleParams): Promise<ImageResponse>;
/**
* Start a txt2img pipeline.
*/
txt2img(model: ModelParams, params: Txt2ImgParams, upscale?: UpscaleParams): Promise<ImageResponse>;
/**
* Start an im2img pipeline.
*/
img2img(model: ModelParams, params: Img2ImgParams, upscale?: UpscaleParams): Promise<ImageResponse>;
/**
* Start an inpaint pipeline.
*/
inpaint(model: ModelParams, params: InpaintParams, upscale?: UpscaleParams): Promise<ImageResponse>;
/**
* Start an outpaint pipeline.
*/
outpaint(model: ModelParams, params: OutpaintParams, upscale?: UpscaleParams): Promise<ImageResponse>;
/**
* Start an upscale pipeline.
*/
upscale(model: ModelParams, params: UpscaleReqParams, upscale?: UpscaleParams): Promise<ImageResponse>;
/**
* Check whether some pipeline's output is ready yet.
*/
ready(params: ImageResponse): Promise<ReadyResponse>;
}
export const STATUS_SUCCESS = 200;
export function paramsFromConfig(defaults: ServerParams): Required<BaseImgParams> {
return {
cfg: defaults.cfg.default,
negativePrompt: defaults.negativePrompt.default,
prompt: defaults.prompt.default,
scheduler: defaults.scheduler.default,
steps: defaults.steps.default,
seed: defaults.seed.default,
};
}
/**
* 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: ImageResponse, b: ImageResponse): boolean {
return a.output === b.output;
}
/**
* Join URL path segments, which always use a forward slash per https://www.rfc-editor.org/rfc/rfc1738
*/
export function joinPath(...parts: Array<string>): string {
return parts.join('/');
}
/**
* Build the URL to an API endpoint, given the API root and a list of segments.
*/
export function makeApiUrl(root: string, ...path: Array<string>) {
return new URL(joinPath('api', ...path), root);
}
/**
* Build the URL for an image request, including all of the base image parameters.
*/
export function makeImageURL(root: string, type: string, params: BaseImgParams): URL {
const url = makeApiUrl(root, type);
url.searchParams.append('cfg', params.cfg.toFixed(FIXED_FLOAT));
@ -172,6 +274,9 @@ export function makeImageURL(root: string, type: string, params: BaseImgParams):
return url;
}
/**
* Append the model parameters to an existing URL.
*/
export function appendModelToURL(url: URL, params: ModelParams) {
url.searchParams.append('model', params.model);
url.searchParams.append('platform', params.platform);
@ -179,6 +284,9 @@ export function appendModelToURL(url: URL, params: ModelParams) {
url.searchParams.append('correction', params.correction);
}
/**
* Append the upscale parameters to an existing URL.
*/
export function appendUpscaleToURL(url: URL, upscale: UpscaleParams) {
if (upscale.enabled) {
url.searchParams.append('denoise', upscale.denoise.toFixed(FIXED_FLOAT));
@ -189,6 +297,9 @@ export function appendUpscaleToURL(url: URL, upscale: UpscaleParams) {
}
}
/**
* Make an API client using the given API root and fetch client.
*/
export function makeClient(root: string, f = fetch): ApiClient {
let pending: Promise<ImageResponse> | undefined;
@ -388,6 +499,12 @@ export function makeClient(root: string, f = fetch): ApiClient {
};
}
/**
* Parse a successful API response into the full image response record.
*
* The server sends over the output key, and the client is in the best position to turn
* that into a full URL, since it already knows the root URL of the server.
*/
export async function parseApiResponse(root: string, res: Response): Promise<ImageResponse> {
type LimitedResponse = Omit<ImageResponse, 'output'> & { output: string };

View File

@ -14,23 +14,40 @@ export interface ConfigString {
keys: Array<string>;
}
/**
* Helper type to filter keys whose value extends `TValid`.
*/
export type KeyFilter<T extends object, TValid = number | string> = {
[K in keyof T]: T[K] extends TValid ? K : never;
}[keyof T];
/**
* Keep fields with a file-like value, but make them optional.
*/
export type ConfigFiles<T extends object> = {
[K in KeyFilter<T, Blob | File>]: Maybe<T[K]>;
};
/**
* Map numbers and strings to their corresponding config types and drop the rest of the fields.
*/
export type ConfigRanges<T extends object> = {
[K in KeyFilter<T>]: T[K] extends number ? ConfigNumber : T[K] extends string ? ConfigString : never;
};
/**
* Keep fields whose value extends `TValid` and drop the rest.
*/
export type ConfigState<T extends object, TValid = number | string> = {
[K in KeyFilter<T, TValid>]: T[K] extends TValid ? T[K] : never;
};
// eslint does not understand how to indent this and expects each line to increase
/* eslint-disable */
/**
* Combine all of the request parameter groups, make optional parameters required, then
* map them to the number/string ranges.
*/
export type ServerParams = ConfigRanges<Required<
Img2ImgParams &
Txt2ImgParams &
@ -43,6 +60,9 @@ export type ServerParams = ConfigRanges<Required<
};
/* eslint-enable */
/**
* Parameters that can be customized on the client, through the config file or settings tab.
*/
export interface ClientParams {
model: ConfigString;
platform: ConfigString;
@ -57,11 +77,6 @@ export interface Config<T = ClientParams> {
params: T;
}
export const DEFAULT_BRUSH = {
color: 255,
size: 8,
};
export const IMAGE_FILTER = '.bmp, .jpg, .jpeg, .png';
export const PARAM_VERSION = '>=0.4.0';

View File

@ -12,15 +12,47 @@ import {
InpaintParams,
ModelParams,
OutpaintPixels,
paramsFromConfig,
Txt2ImgParams,
UpscaleParams,
UpscaleReqParams,
} from './client.js';
import { Config, ConfigFiles, ConfigState, ServerParams } from './config.js';
/**
* Combine optional files and required ranges.
*/
type TabState<TabParams> = ConfigFiles<Required<TabParams>> & ConfigState<Required<TabParams>>;
interface BrushSlice {
brush: BrushParams;
setBrush(brush: Partial<BrushParams>): void;
}
interface DefaultSlice {
defaults: TabState<BaseImgParams>;
setDefaults(param: Partial<BaseImgParams>): void;
}
interface HistorySlice {
history: Array<ImageResponse>;
limit: number;
loading: Maybe<ImageResponse>;
pushHistory(image: ImageResponse): void;
removeHistory(image: ImageResponse): void;
setLimit(limit: number): void;
setLoading(image: Maybe<ImageResponse>): void;
}
interface ModelSlice {
model: ModelParams;
setModel(model: Partial<ModelParams>): void;
}
// #region tab slices
interface Txt2ImgSlice {
txt2img: TabState<Txt2ImgParams>;
@ -42,35 +74,12 @@ interface InpaintSlice {
resetInpaint(): void;
}
interface HistorySlice {
history: Array<ImageResponse>;
limit: number;
loading: Maybe<ImageResponse>;
pushHistory(image: ImageResponse): void;
removeHistory(image: ImageResponse): void;
setLimit(limit: number): void;
setLoading(image: Maybe<ImageResponse>): void;
}
interface DefaultSlice {
defaults: TabState<BaseImgParams>;
setDefaults(param: Partial<BaseImgParams>): void;
}
interface OutpaintSlice {
outpaint: OutpaintPixels;
setOutpaint(pixels: Partial<OutpaintPixels>): void;
}
interface BrushSlice {
brush: BrushParams;
setBrush(brush: Partial<BrushParams>): void;
}
interface UpscaleSlice {
upscale: UpscaleParams;
upscaleTab: TabState<UpscaleReqParams>;
@ -79,13 +88,11 @@ interface UpscaleSlice {
setUpscaleTab(params: Partial<UpscaleReqParams>): void;
resetUpscaleTab(): void;
}
// #endregion
interface ModelSlice {
model: ModelParams;
setModel(model: Partial<ModelParams>): void;
}
/**
* Full merged state including all slices.
*/
export type OnnxState
= BrushSlice
& DefaultSlice
@ -97,14 +104,85 @@ export type OnnxState
& Txt2ImgSlice
& UpscaleSlice;
export function createStateSlices(base: ServerParams) {
const defaults = paramsFromConfig(base);
/**
* Shorthand for state creator to reduce repeated arguments.
*/
export type Slice<T> = StateCreator<OnnxState, [], [], T>;
const createTxt2ImgSlice: StateCreator<OnnxState, [], [], Txt2ImgSlice> = (set) => ({
/**
* React context binding for API client.
*/
export const ClientContext = createContext<Maybe<ApiClient>>(undefined);
/**
* React context binding for merged config, including server parameters.
*/
export const ConfigContext = createContext<Maybe<Config<ServerParams>>>(undefined);
/**
* React context binding for zustand state store.
*/
export const StateContext = createContext<Maybe<StoreApi<OnnxState>>>(undefined);
/**
* Current state version for zustand persistence.
*/
export const STATE_VERSION = 4;
/**
* Default parameters for the inpaint brush.
*
* Not provided by the server yet.
*/
export const DEFAULT_BRUSH = {
color: 255,
size: 8,
strength: 0.5,
};
/**
* Default parameters for the image history.
*
* Not provided by the server yet.
*/
export const DEFAULT_HISTORY = {
/**
* The number of images to be shown.
*/
limit: 4,
/**
* The number of additional images to be kept in history, so they can scroll
* back into view when you delete one. Does not include deleted images.
*/
scrollback: 2,
};
export function baseParamsFromServer(defaults: ServerParams): Required<BaseImgParams> {
return {
cfg: defaults.cfg.default,
negativePrompt: defaults.negativePrompt.default,
prompt: defaults.prompt.default,
scheduler: defaults.scheduler.default,
steps: defaults.steps.default,
seed: defaults.seed.default,
};
}
/**
* Prepare the state slice constructors.
*
* In the default state, image sources should be null and booleans should be false. Everything
* else should be initialized from the default value in the base parameters.
*/
export function createStateSlices(server: ServerParams) {
const base = baseParamsFromServer(server);
const createTxt2ImgSlice: Slice<Txt2ImgSlice> = (set) => ({
txt2img: {
...defaults,
width: base.width.default,
height: base.height.default,
...base,
width: server.width.default,
height: server.height.default,
},
setTxt2Img(params) {
set((prev) => ({
@ -117,19 +195,19 @@ export function createStateSlices(base: ServerParams) {
resetTxt2Img() {
set({
txt2img: {
...defaults,
width: base.width.default,
height: base.height.default,
...base,
width: server.width.default,
height: server.height.default,
},
});
},
});
const createImg2ImgSlice: StateCreator<OnnxState, [], [], Img2ImgSlice> = (set) => ({
const createImg2ImgSlice: Slice<Img2ImgSlice> = (set) => ({
img2img: {
...defaults,
...base,
source: null,
strength: base.strength.default,
strength: server.strength.default,
},
setImg2Img(params) {
set((prev) => ({
@ -142,23 +220,23 @@ export function createStateSlices(base: ServerParams) {
resetImg2Img() {
set({
img2img: {
...defaults,
...base,
source: null,
strength: base.strength.default,
strength: server.strength.default,
},
});
},
});
const createInpaintSlice: StateCreator<OnnxState, [], [], InpaintSlice> = (set) => ({
const createInpaintSlice: Slice<InpaintSlice> = (set) => ({
inpaint: {
...defaults,
fillColor: '#000000',
filter: 'none',
...base,
fillColor: server.fillColor.default,
filter: server.filter.default,
mask: null,
noise: 'histogram',
noise: server.noise.default,
source: null,
strength: 1.0,
strength: server.strength.default,
},
setInpaint(params) {
set((prev) => ({
@ -171,21 +249,21 @@ export function createStateSlices(base: ServerParams) {
resetInpaint() {
set({
inpaint: {
...defaults,
fillColor: '#000000',
filter: 'none',
...base,
fillColor: server.fillColor.default,
filter: server.filter.default,
mask: null,
noise: 'histogram',
noise: server.noise.default,
source: null,
strength: 1.0,
strength: server.strength.default,
},
});
},
});
const createHistorySlice: StateCreator<OnnxState, [], [], HistorySlice> = (set) => ({
const createHistorySlice: Slice<HistorySlice> = (set) => ({
history: [],
limit: 4,
limit: DEFAULT_HISTORY.limit,
loading: null,
pushHistory(image) {
set((prev) => ({
@ -193,7 +271,7 @@ export function createStateSlices(base: ServerParams) {
history: [
image,
...prev.history,
].slice(0, prev.limit),
].slice(0, prev.limit + DEFAULT_HISTORY.scrollback),
loading: null,
}));
},
@ -217,13 +295,13 @@ export function createStateSlices(base: ServerParams) {
},
});
const createOutpaintSlice: StateCreator<OnnxState, [], [], OutpaintSlice> = (set) => ({
const createOutpaintSlice: Slice<OutpaintSlice> = (set) => ({
outpaint: {
enabled: false,
left: 0,
right: 0,
top: 0,
bottom: 0,
left: server.left.default,
right: server.right.default,
top: server.top.default,
bottom: server.bottom.default,
},
setOutpaint(pixels) {
set((prev) => ({
@ -235,11 +313,9 @@ export function createStateSlices(base: ServerParams) {
},
});
const createBrushSlice: StateCreator<OnnxState, [], [], BrushSlice> = (set) => ({
const createBrushSlice: Slice<BrushSlice> = (set) => ({
brush: {
color: 255,
size: 8,
strength: 0.5,
...DEFAULT_BRUSH,
},
setBrush(brush) {
set((prev) => ({
@ -251,14 +327,14 @@ export function createStateSlices(base: ServerParams) {
},
});
const createUpscaleSlice: StateCreator<OnnxState, [], [], UpscaleSlice> = (set) => ({
const createUpscaleSlice: Slice<UpscaleSlice> = (set) => ({
upscale: {
denoise: 0.5,
denoise: server.denoise.default,
enabled: false,
faces: false,
scale: 1,
outscale: 1,
faceStrength: 0.5,
scale: server.scale.default,
outscale: server.outscale.default,
faceStrength: server.faceStrength.default,
},
upscaleTab: {
source: null,
@ -288,9 +364,9 @@ export function createStateSlices(base: ServerParams) {
},
});
const createDefaultSlice: StateCreator<OnnxState, [], [], DefaultSlice> = (set) => ({
const createDefaultSlice: Slice<DefaultSlice> = (set) => ({
defaults: {
...defaults,
...base,
},
setDefaults(params) {
set((prev) => ({
@ -302,12 +378,12 @@ export function createStateSlices(base: ServerParams) {
},
});
const createModelSlice: StateCreator<OnnxState, [], [], ModelSlice> = (set) => ({
const createModelSlice: Slice<ModelSlice> = (set) => ({
model: {
model: '',
platform: '',
upscaling: '',
correction: '',
model: server.model.default,
platform: server.platform.default,
upscaling: server.upscaling.default,
correction: server.correction.default,
},
setModel(params) {
set((prev) => ({
@ -331,8 +407,3 @@ export function createStateSlices(base: ServerParams) {
createUpscaleSlice,
};
}
export const ClientContext = createContext<Maybe<ApiClient>>(undefined);
export const ConfigContext = createContext<Maybe<Config<ServerParams>>>(undefined);
export const StateContext = createContext<Maybe<StoreApi<OnnxState>>>(undefined);
export const STATE_VERSION = 4;