mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): generate iterations graph
This commit is contained in:
committed by
psychedelicious
parent
18aa0c91da
commit
2386d5d786
@@ -1,9 +1,13 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { RootState } from 'app/store';
|
||||
import { InvokeTabName, tabMap } from 'features/ui/store/tabMap';
|
||||
import { Graph } from 'services/api';
|
||||
import { buildImg2ImgNode, buildTxt2ImgNode } from './buildNodes';
|
||||
import {
|
||||
Graph,
|
||||
ImageToImageInvocation,
|
||||
TextToImageInvocation,
|
||||
} from 'services/api';
|
||||
import { buildTxt2ImgNode } from './nodes/textToimage';
|
||||
import { buildImg2ImgNode } from './nodes/imageToImage';
|
||||
import { buildIteration } from './nodes/iteration';
|
||||
|
||||
function mapTabToFunction(activeTabName: InvokeTabName) {
|
||||
switch (activeTabName) {
|
||||
@@ -18,74 +22,22 @@ function mapTabToFunction(activeTabName: InvokeTabName) {
|
||||
}
|
||||
}
|
||||
|
||||
const exampleGraphs: Record<string, Graph> = {
|
||||
iterations: {
|
||||
nodes: {
|
||||
'0': {
|
||||
id: '0',
|
||||
type: 'range',
|
||||
start: 0,
|
||||
stop: 5,
|
||||
step: 1,
|
||||
},
|
||||
'1': {
|
||||
collection: [],
|
||||
id: '1',
|
||||
index: 0,
|
||||
type: 'iterate',
|
||||
},
|
||||
'2': {
|
||||
cfg_scale: 7.5,
|
||||
height: 512,
|
||||
id: '2',
|
||||
model: '',
|
||||
progress_images: false,
|
||||
prompt: 'dog',
|
||||
sampler_name: 'k_lms',
|
||||
seamless: false,
|
||||
steps: 11,
|
||||
type: 'txt2img',
|
||||
width: 512,
|
||||
},
|
||||
},
|
||||
edges: [
|
||||
{
|
||||
source: {
|
||||
field: 'collection',
|
||||
node_id: '0',
|
||||
},
|
||||
destination: {
|
||||
field: 'collection',
|
||||
node_id: '1',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
field: 'item',
|
||||
node_id: '1',
|
||||
},
|
||||
destination: {
|
||||
field: 'seed',
|
||||
node_id: '2',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
const buildBaseNode = (
|
||||
state: RootState
|
||||
): Record<string, TextToImageInvocation | ImageToImageInvocation> => {
|
||||
const { activeTab } = state.ui;
|
||||
const activeTabName = tabMap[activeTab];
|
||||
|
||||
return mapTabToFunction(activeTabName)(state);
|
||||
};
|
||||
|
||||
export const buildGraph = (state: RootState): Graph => {
|
||||
const { activeTab } = state.ui;
|
||||
const activeTabName = tabMap[activeTab];
|
||||
const nodeId = uuidv4();
|
||||
const { iterations } = state.generation;
|
||||
const baseNode = buildBaseNode(state);
|
||||
|
||||
// return exampleGraphs.iterations;
|
||||
if (iterations > 1) {
|
||||
return buildIteration({ baseNode, iterations });
|
||||
}
|
||||
|
||||
return {
|
||||
nodes: {
|
||||
[nodeId]: {
|
||||
id: nodeId,
|
||||
...mapTabToFunction(activeTabName)(state),
|
||||
},
|
||||
},
|
||||
};
|
||||
return { nodes: baseNode };
|
||||
};
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
import { RootState } from 'app/store';
|
||||
import {
|
||||
ImageToImageInvocation,
|
||||
RestoreFaceInvocation,
|
||||
TextToImageInvocation,
|
||||
UpscaleInvocation,
|
||||
} from 'services/api';
|
||||
|
||||
import { _Image } from 'app/invokeai';
|
||||
import { initialImageSelector } from 'features/parameters/store/generationSelectors';
|
||||
|
||||
// fe todo fix model type (frontend uses null, backend uses undefined)
|
||||
// fe todo update front end to store to have whole image field (vs just name)
|
||||
// be todo add symmetry fields
|
||||
// be todo variations....
|
||||
|
||||
export function buildTxt2ImgNode(
|
||||
state: RootState
|
||||
): Omit<TextToImageInvocation, 'id'> {
|
||||
const { generation, system } = state;
|
||||
|
||||
const { shouldDisplayInProgressType, model } = system;
|
||||
|
||||
const {
|
||||
prompt,
|
||||
seed,
|
||||
steps,
|
||||
width,
|
||||
height,
|
||||
cfgScale: cfg_scale,
|
||||
sampler,
|
||||
seamless,
|
||||
shouldRandomizeSeed,
|
||||
} = generation;
|
||||
|
||||
// missing fields in TextToImageInvocation: strength, hires_fix
|
||||
return {
|
||||
type: 'txt2img',
|
||||
prompt,
|
||||
seed: shouldRandomizeSeed ? -1 : seed,
|
||||
steps,
|
||||
width,
|
||||
height,
|
||||
cfg_scale,
|
||||
sampler_name: sampler as TextToImageInvocation['sampler_name'],
|
||||
seamless,
|
||||
model,
|
||||
progress_images: shouldDisplayInProgressType === 'full-res',
|
||||
};
|
||||
}
|
||||
|
||||
export function buildImg2ImgNode(
|
||||
state: RootState
|
||||
): Omit<ImageToImageInvocation, 'id'> {
|
||||
const { generation, system } = state;
|
||||
|
||||
const { shouldDisplayInProgressType, model } = system;
|
||||
|
||||
const {
|
||||
prompt,
|
||||
seed,
|
||||
steps,
|
||||
width,
|
||||
height,
|
||||
cfgScale,
|
||||
sampler,
|
||||
seamless,
|
||||
img2imgStrength: strength,
|
||||
shouldFitToWidthHeight: fit,
|
||||
shouldRandomizeSeed,
|
||||
} = generation;
|
||||
|
||||
const initialImage = initialImageSelector(state);
|
||||
|
||||
if (!initialImage) {
|
||||
// TODO: handle this
|
||||
throw 'no initial image';
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'img2img',
|
||||
prompt,
|
||||
seed: shouldRandomizeSeed ? -1 : seed,
|
||||
steps,
|
||||
width,
|
||||
height,
|
||||
cfg_scale: cfgScale,
|
||||
sampler_name: sampler as ImageToImageInvocation['sampler_name'],
|
||||
seamless,
|
||||
model,
|
||||
progress_images: shouldDisplayInProgressType === 'full-res',
|
||||
image: {
|
||||
image_name: initialImage.name,
|
||||
image_type: initialImage.type,
|
||||
},
|
||||
strength,
|
||||
fit,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildFacetoolNode(
|
||||
state: RootState
|
||||
): Omit<RestoreFaceInvocation, 'id'> {
|
||||
const { generation, postprocessing } = state;
|
||||
|
||||
const { initialImage } = generation;
|
||||
|
||||
const { facetoolStrength: strength } = postprocessing;
|
||||
|
||||
// missing fields in RestoreFaceInvocation: type, codeformer_fidelity
|
||||
return {
|
||||
type: 'restore_face',
|
||||
image: {
|
||||
image_name:
|
||||
(typeof initialImage === 'string' ? initialImage : initialImage?.url) ||
|
||||
'',
|
||||
image_type: 'results',
|
||||
},
|
||||
strength,
|
||||
};
|
||||
}
|
||||
|
||||
// is this ESRGAN??
|
||||
export function buildUpscaleNode(
|
||||
state: RootState
|
||||
): Omit<UpscaleInvocation, 'id'> {
|
||||
const { generation, postprocessing } = state;
|
||||
|
||||
const { initialImage } = generation;
|
||||
|
||||
const { upscalingLevel: level, upscalingStrength: strength } = postprocessing;
|
||||
|
||||
// missing fields in UpscaleInvocation: denoise_str
|
||||
return {
|
||||
type: 'upscale',
|
||||
image: {
|
||||
image_name:
|
||||
(typeof initialImage === 'string' ? initialImage : initialImage?.url) ||
|
||||
'',
|
||||
image_type: 'results',
|
||||
},
|
||||
strength,
|
||||
level,
|
||||
};
|
||||
}
|
||||
58
invokeai/frontend/web/src/common/util/nodes/imageToImage.ts
Normal file
58
invokeai/frontend/web/src/common/util/nodes/imageToImage.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { RootState } from 'app/store';
|
||||
import { ImageToImageInvocation } from 'services/api';
|
||||
import { _Image } from 'app/invokeai';
|
||||
import { initialImageSelector } from 'features/parameters/store/generationSelectors';
|
||||
|
||||
export function buildImg2ImgNode(
|
||||
state: RootState
|
||||
): Record<string, ImageToImageInvocation> {
|
||||
const nodeId = uuidv4();
|
||||
const { generation, system } = state;
|
||||
|
||||
const { shouldDisplayInProgressType, model } = system;
|
||||
|
||||
const {
|
||||
prompt,
|
||||
seed,
|
||||
steps,
|
||||
width,
|
||||
height,
|
||||
cfgScale,
|
||||
sampler,
|
||||
seamless,
|
||||
img2imgStrength: strength,
|
||||
shouldFitToWidthHeight: fit,
|
||||
shouldRandomizeSeed,
|
||||
} = generation;
|
||||
|
||||
const initialImage = initialImageSelector(state);
|
||||
|
||||
if (!initialImage) {
|
||||
// TODO: handle this
|
||||
throw 'no initial image';
|
||||
}
|
||||
|
||||
return {
|
||||
[nodeId]: {
|
||||
id: nodeId,
|
||||
type: 'img2img',
|
||||
prompt,
|
||||
seed: shouldRandomizeSeed ? -1 : seed,
|
||||
steps,
|
||||
width,
|
||||
height,
|
||||
cfg_scale: cfgScale,
|
||||
sampler_name: sampler as ImageToImageInvocation['sampler_name'],
|
||||
seamless,
|
||||
model,
|
||||
progress_images: shouldDisplayInProgressType === 'full-res',
|
||||
image: {
|
||||
image_name: initialImage.name,
|
||||
image_type: initialImage.type,
|
||||
},
|
||||
strength,
|
||||
fit,
|
||||
},
|
||||
};
|
||||
}
|
||||
79
invokeai/frontend/web/src/common/util/nodes/iteration.ts
Normal file
79
invokeai/frontend/web/src/common/util/nodes/iteration.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import {
|
||||
Edge,
|
||||
Graph,
|
||||
ImageToImageInvocation,
|
||||
IterateInvocation,
|
||||
RangeInvocation,
|
||||
TextToImageInvocation,
|
||||
} from 'services/api';
|
||||
|
||||
type BuildIteration = {
|
||||
baseNode: Record<string, TextToImageInvocation | ImageToImageInvocation>;
|
||||
iterations: number;
|
||||
};
|
||||
|
||||
const buildRangeNode = (
|
||||
iterations: number
|
||||
): Record<string, RangeInvocation> => {
|
||||
const nodeId = uuidv4();
|
||||
return {
|
||||
[nodeId]: {
|
||||
id: nodeId,
|
||||
type: 'range',
|
||||
start: 0,
|
||||
stop: iterations,
|
||||
step: 1,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const buildIterateNode = (): Record<string, IterateInvocation> => {
|
||||
const nodeId = uuidv4();
|
||||
return {
|
||||
[nodeId]: {
|
||||
id: nodeId,
|
||||
type: 'iterate',
|
||||
collection: [],
|
||||
index: 0,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const buildIteration = ({
|
||||
baseNode,
|
||||
iterations,
|
||||
}: BuildIteration): Graph => {
|
||||
const rangeNode = buildRangeNode(iterations);
|
||||
const iterateNode = buildIterateNode();
|
||||
const edges: Edge[] = [
|
||||
{
|
||||
source: {
|
||||
field: 'collection',
|
||||
node_id: Object.keys(rangeNode)[0],
|
||||
},
|
||||
destination: {
|
||||
field: 'collection',
|
||||
node_id: Object.keys(iterateNode)[0],
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
field: 'item',
|
||||
node_id: Object.keys(iterateNode)[0],
|
||||
},
|
||||
destination: {
|
||||
field: 'seed',
|
||||
node_id: Object.keys(baseNode)[0],
|
||||
},
|
||||
},
|
||||
];
|
||||
return {
|
||||
nodes: {
|
||||
...rangeNode,
|
||||
...iterateNode,
|
||||
...baseNode,
|
||||
},
|
||||
edges,
|
||||
};
|
||||
};
|
||||
43
invokeai/frontend/web/src/common/util/nodes/textToImage.ts
Normal file
43
invokeai/frontend/web/src/common/util/nodes/textToImage.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { RootState } from 'app/store';
|
||||
import { TextToImageInvocation } from 'services/api';
|
||||
import { _Image } from 'app/invokeai';
|
||||
|
||||
export function buildTxt2ImgNode(
|
||||
state: RootState
|
||||
): Record<string, TextToImageInvocation> {
|
||||
const nodeId = uuidv4();
|
||||
const { generation, system } = state;
|
||||
|
||||
const { shouldDisplayInProgressType, model } = system;
|
||||
|
||||
const {
|
||||
prompt,
|
||||
seed,
|
||||
steps,
|
||||
width,
|
||||
height,
|
||||
cfgScale: cfg_scale,
|
||||
sampler,
|
||||
seamless,
|
||||
shouldRandomizeSeed,
|
||||
} = generation;
|
||||
|
||||
// missing fields in TextToImageInvocation: strength, hires_fix
|
||||
return {
|
||||
[nodeId]: {
|
||||
id: nodeId,
|
||||
type: 'txt2img',
|
||||
prompt,
|
||||
seed: shouldRandomizeSeed ? -1 : seed,
|
||||
steps,
|
||||
width,
|
||||
height,
|
||||
cfg_scale,
|
||||
sampler_name: sampler as TextToImageInvocation['sampler_name'],
|
||||
seamless,
|
||||
model,
|
||||
progress_images: shouldDisplayInProgressType === 'full-res',
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user