a whole bunch of stuff

This commit is contained in:
Mary Hipp 2024-08-06 15:31:13 -04:00
parent 857d74bbfe
commit 2604fd9fde
36 changed files with 686 additions and 472 deletions

View File

@ -1,19 +1,13 @@
from typing import Optional from fastapi import APIRouter, Body, HTTPException, Path
from fastapi import APIRouter, Body, HTTPException, Path, Query
from invokeai.app.api.dependencies import ApiDependencies from invokeai.app.api.dependencies import ApiDependencies
from invokeai.app.services.shared.pagination import PaginatedResults
from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection
from invokeai.app.services.style_preset_records.style_preset_records_common import ( from invokeai.app.services.style_preset_records.style_preset_records_common import (
StylePresetChanges, StylePresetChanges,
StylePresetNotFoundError, StylePresetNotFoundError,
StylePresetRecordDTO, StylePresetRecordDTO,
StylePresetWithoutId, StylePresetWithoutId,
StylePresetRecordOrderBy,
) )
style_presets_router = APIRouter(prefix="/v1/style_presets", tags=["style_presets"]) style_presets_router = APIRouter(prefix="/v1/style_presets", tags=["style_presets"])
@ -78,23 +72,9 @@ async def create_style_preset(
"/", "/",
operation_id="list_style_presets", operation_id="list_style_presets",
responses={ responses={
200: {"model": PaginatedResults[StylePresetRecordDTO]}, 200: {"model": list[StylePresetRecordDTO]},
}, },
) )
async def list_style_presets( async def list_style_presets() -> list[StylePresetRecordDTO]:
page: int = Query(default=0, description="The page to get"),
per_page: int = Query(default=10, description="The number of style presets per page"),
order_by: StylePresetRecordOrderBy = Query(
default=StylePresetRecordOrderBy.Name, description="The attribute to order by"
),
direction: SQLiteDirection = Query(default=SQLiteDirection.Ascending, description="The direction to order by"),
query: Optional[str] = Query(default=None, description="The text to query by (matches name and description)"),
) -> PaginatedResults[StylePresetRecordDTO]:
"""Gets a page of style presets""" """Gets a page of style presets"""
return ApiDependencies.invoker.services.style_preset_records.get_many( return ApiDependencies.invoker.services.style_preset_records.get_many()
page=page,
per_page=per_page,
order_by=order_by,
direction=direction,
query=query,
)

View File

@ -1,13 +1,9 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Optional
from invokeai.app.services.shared.pagination import PaginatedResults
from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection
from invokeai.app.services.style_preset_records.style_preset_records_common import ( from invokeai.app.services.style_preset_records.style_preset_records_common import (
StylePresetChanges, StylePresetChanges,
StylePresetRecordDTO, StylePresetRecordDTO,
StylePresetWithoutId, StylePresetWithoutId,
StylePresetRecordOrderBy,
) )
@ -35,13 +31,6 @@ class StylePresetRecordsStorageBase(ABC):
pass pass
@abstractmethod @abstractmethod
def get_many( def get_many(self) -> list[StylePresetRecordDTO]:
self,
page: int,
per_page: int,
order_by: StylePresetRecordOrderBy,
direction: SQLiteDirection,
query: Optional[str],
) -> PaginatedResults[StylePresetRecordDTO]:
"""Gets many workflows.""" """Gets many workflows."""
pass pass

View File

@ -10,13 +10,6 @@ class StylePresetNotFoundError(Exception):
"""Raised when a style preset is not found""" """Raised when a style preset is not found"""
class StylePresetRecordOrderBy(str, Enum, metaclass=MetaEnum):
"""The order by options for workflow records"""
CreatedAt = "created_at"
Name = "name"
class PresetData(BaseModel, extra="forbid"): class PresetData(BaseModel, extra="forbid"):
positive_prompt: str = Field(description="Positive prompt") positive_prompt: str = Field(description="Positive prompt")
negative_prompt: str = Field(description="Negative prompt") negative_prompt: str = Field(description="Negative prompt")

View File

@ -11,7 +11,6 @@ from invokeai.app.services.style_preset_records.style_preset_records_common impo
StylePresetNotFoundError, StylePresetNotFoundError,
StylePresetRecordDTO, StylePresetRecordDTO,
StylePresetWithoutId, StylePresetWithoutId,
StylePresetRecordOrderBy,
) )
from invokeai.app.util.misc import uuid_string from invokeai.app.util.misc import uuid_string
@ -128,50 +127,21 @@ class SqliteStylePresetRecordsStorage(StylePresetRecordsStorageBase):
def get_many( def get_many(
self, self,
page: int, ) -> list[StylePresetRecordDTO]:
per_page: int,
order_by: StylePresetRecordOrderBy,
direction: SQLiteDirection,
query: Optional[str] = None,
) -> PaginatedResults[StylePresetRecordDTO]:
try: try:
self._lock.acquire() self._lock.acquire()
# sanitize!
assert order_by in StylePresetRecordOrderBy
assert direction in SQLiteDirection
count_query = "SELECT COUNT(*) FROM style_presets"
main_query = """ main_query = """
SELECT SELECT
* *
FROM style_presets FROM style_presets
ORDER BY name ASC
""" """
main_params: list[int | str] = []
count_params: list[int | str] = []
stripped_query = query.strip() if query else None
if stripped_query:
wildcard_query = "%" + stripped_query + "%"
main_query += " AND name LIKE ? "
count_query += " AND name LIKE ?;"
main_params.extend([wildcard_query, wildcard_query])
count_params.extend([wildcard_query, wildcard_query])
main_query += f" ORDER BY {order_by.value} {direction.value} LIMIT ? OFFSET ?;" self._cursor.execute(main_query)
main_params.extend([per_page, page * per_page])
self._cursor.execute(main_query, main_params)
rows = self._cursor.fetchall() rows = self._cursor.fetchall()
style_presets = [StylePresetRecordDTO.from_dict(dict(row)) for row in rows] style_presets = [StylePresetRecordDTO.from_dict(dict(row)) for row in rows]
self._cursor.execute(count_query, count_params) return style_presets
total = self._cursor.fetchone()[0]
pages = total // per_page + (total % per_page > 0)
return PaginatedResults(
items=style_presets,
page=page,
per_page=per_page,
pages=pages,
total=total,
)
except Exception: except Exception:
self._conn.rollback() self._conn.rollback()
raise raise

View File

@ -53,7 +53,6 @@ import type { AppDispatch, RootState } from 'app/store/store';
import { addArchivedOrDeletedBoardListener } from './listeners/addArchivedOrDeletedBoardListener'; import { addArchivedOrDeletedBoardListener } from './listeners/addArchivedOrDeletedBoardListener';
import { addEnqueueRequestedUpscale } from './listeners/enqueueRequestedUpscale'; import { addEnqueueRequestedUpscale } from './listeners/enqueueRequestedUpscale';
import { addActiveStylePresetChanged } from './listeners/activeStylePresetChanged';
export const listenerMiddleware = createListenerMiddleware(); export const listenerMiddleware = createListenerMiddleware();
@ -147,7 +146,6 @@ addAdHocPostProcessingRequestedListener(startAppListening);
// Prompts // Prompts
addDynamicPromptsListener(startAppListening); addDynamicPromptsListener(startAppListening);
addActiveStylePresetChanged(startAppListening)
addSetDefaultSettingsListener(startAppListening); addSetDefaultSettingsListener(startAppListening);
addControlAdapterPreprocessor(startAppListening); addControlAdapterPreprocessor(startAppListening);

View File

@ -1,31 +0,0 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { negativePromptChanged, positivePromptChanged, } from 'features/controlLayers/store/controlLayersSlice';
import { activeStylePresetChanged, calculatedNegPromptChanged, calculatedPosPromptChanged } from '../../../../../features/stylePresets/store/stylePresetSlice';
import { isAnyOf } from '@reduxjs/toolkit';
export const addActiveStylePresetChanged = (startAppListening: AppStartListening) => {
startAppListening({
matcher: isAnyOf(activeStylePresetChanged, positivePromptChanged, negativePromptChanged),
effect: async (action, { dispatch, getState }) => {
const state = getState();
const activeStylePreset = state.stylePreset.activeStylePreset;
const positivePrompt = state.controlLayers.present.positivePrompt
const negativePrompt = state.controlLayers.present.negativePrompt
if (!activeStylePreset) {
return;
}
const { positive_prompt: presetPositivePrompt, negative_prompt: presetNegativePrompt } = activeStylePreset.preset_data;
const calculatedPosPrompt = presetPositivePrompt.includes('{prompt}') ? presetPositivePrompt.replace('{prompt}', positivePrompt) : `${positivePrompt} ${presetPositivePrompt}`
const calculatedNegPrompt = presetNegativePrompt.includes('{prompt}') ? presetNegativePrompt.replace('{prompt}', negativePrompt) : `${negativePrompt} ${presetNegativePrompt}`
dispatch(calculatedPosPromptChanged(calculatedPosPrompt))
dispatch(calculatedNegPromptChanged(calculatedNegPrompt))
},
});
};

View File

@ -29,6 +29,7 @@ import { upscalePersistConfig, upscaleSlice } from 'features/parameters/store/up
import { queueSlice } from 'features/queue/store/queueSlice'; import { queueSlice } from 'features/queue/store/queueSlice';
import { sdxlPersistConfig, sdxlSlice } from 'features/sdxl/store/sdxlSlice'; import { sdxlPersistConfig, sdxlSlice } from 'features/sdxl/store/sdxlSlice';
import { stylePresetModalSlice } from 'features/stylePresets/store/stylePresetModalSlice'; import { stylePresetModalSlice } from 'features/stylePresets/store/stylePresetModalSlice';
import { stylePresetSlice } from 'features/stylePresets/store/stylePresetSlice';
import { configSlice } from 'features/system/store/configSlice'; import { configSlice } from 'features/system/store/configSlice';
import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice'; import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice';
import { uiPersistConfig, uiSlice } from 'features/ui/store/uiSlice'; import { uiPersistConfig, uiSlice } from 'features/ui/store/uiSlice';
@ -47,7 +48,6 @@ import { actionSanitizer } from './middleware/devtools/actionSanitizer';
import { actionsDenylist } from './middleware/devtools/actionsDenylist'; import { actionsDenylist } from './middleware/devtools/actionsDenylist';
import { stateSanitizer } from './middleware/devtools/stateSanitizer'; import { stateSanitizer } from './middleware/devtools/stateSanitizer';
import { listenerMiddleware } from './middleware/listenerMiddleware'; import { listenerMiddleware } from './middleware/listenerMiddleware';
import { stylePresetSlice } from '../../features/stylePresets/store/stylePresetSlice';
const allReducers = { const allReducers = {
[canvasSlice.name]: canvasSlice.reducer, [canvasSlice.name]: canvasSlice.reducer,

View File

@ -22,11 +22,10 @@ import {
} from './constants'; } from './constants';
import { addLoRAs } from './generation/addLoRAs'; import { addLoRAs } from './generation/addLoRAs';
import { addSDXLLoRas } from './generation/addSDXLLoRAs'; import { addSDXLLoRas } from './generation/addSDXLLoRAs';
import { getBoardField, getSDXLStylePrompts } from './graphBuilderUtils'; import { getBoardField, getPresetModifiedPrompts } from './graphBuilderUtils';
export const buildMultidiffusionUpscaleGraph = async (state: RootState): Promise<GraphType> => { export const buildMultidiffusionUpscaleGraph = async (state: RootState): Promise<GraphType> => {
const { model, cfgScale: cfg_scale, scheduler, steps, vaePrecision, seed, vae } = state.generation; const { model, cfgScale: cfg_scale, scheduler, steps, vaePrecision, seed, vae } = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { upscaleModel, upscaleInitialImage, structure, creativity, tileControlnetModel, scale } = state.upscale; const { upscaleModel, upscaleInitialImage, structure, creativity, tileControlnetModel, scale } = state.upscale;
assert(model, 'No model found in state'); assert(model, 'No model found in state');
@ -99,7 +98,7 @@ export const buildMultidiffusionUpscaleGraph = async (state: RootState): Promise
let modelNode; let modelNode;
if (model.base === 'sdxl') { if (model.base === 'sdxl') {
const { positiveStylePrompt, negativeStylePrompt } = getSDXLStylePrompts(state); const { positivePrompt, negativePrompt, positiveStylePrompt, negativeStylePrompt } = getPresetModifiedPrompts(state);
posCondNode = g.addNode({ posCondNode = g.addNode({
type: 'sdxl_compel_prompt', type: 'sdxl_compel_prompt',
@ -140,6 +139,8 @@ export const buildMultidiffusionUpscaleGraph = async (state: RootState): Promise
vae: vae ?? undefined, vae: vae ?? undefined,
}); });
} else { } else {
const { positivePrompt, negativePrompt } = getPresetModifiedPrompts(state);
posCondNode = g.addNode({ posCondNode = g.addNode({
type: 'compel', type: 'compel',
id: POSITIVE_CONDITIONING, id: POSITIVE_CONDITIONING,

View File

@ -16,7 +16,7 @@ import {
SDXL_REFINER_POSITIVE_CONDITIONING, SDXL_REFINER_POSITIVE_CONDITIONING,
SDXL_REFINER_SEAMLESS, SDXL_REFINER_SEAMLESS,
} from 'features/nodes/util/graph/constants'; } from 'features/nodes/util/graph/constants';
import { getSDXLStylePrompts } from 'features/nodes/util/graph/graphBuilderUtils'; import { getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import type { NonNullableGraph } from 'services/api/types'; import type { NonNullableGraph } from 'services/api/types';
import { isRefinerMainModelModelConfig } from 'services/api/types'; import { isRefinerMainModelModelConfig } from 'services/api/types';
@ -59,7 +59,7 @@ export const addSDXLRefinerToGraph = async (
const modelLoaderId = modelLoaderNodeId ? modelLoaderNodeId : SDXL_MODEL_LOADER; const modelLoaderId = modelLoaderNodeId ? modelLoaderNodeId : SDXL_MODEL_LOADER;
// Construct Style Prompt // Construct Style Prompt
const { positiveStylePrompt, negativeStylePrompt } = getSDXLStylePrompts(state); const { positiveStylePrompt, negativeStylePrompt } = getPresetModifiedPrompts(state);
// Unplug SDXL Latents Generation To Latents To Image // Unplug SDXL Latents Generation To Latents To Image
graph.edges = graph.edges.filter((e) => !(e.source.node_id === baseNodeId && ['latents'].includes(e.source.field))); graph.edges = graph.edges.filter((e) => !(e.source.node_id === baseNodeId && ['latents'].includes(e.source.field)));

View File

@ -16,7 +16,7 @@ import {
POSITIVE_CONDITIONING, POSITIVE_CONDITIONING,
SEAMLESS, SEAMLESS,
} from 'features/nodes/util/graph/constants'; } from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate } from 'features/nodes/util/graph/graphBuilderUtils'; import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types'; import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types';
import { isNonRefinerMainModelConfig } from 'services/api/types'; import { isNonRefinerMainModelConfig } from 'services/api/types';
@ -51,7 +51,6 @@ export const buildCanvasImageToImageGraph = async (
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
// The bounding box determines width and height, not the width and height params // The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions; const { width, height } = state.canvas.boundingBoxDimensions;
@ -71,6 +70,8 @@ export const buildCanvasImageToImageGraph = async (
const use_cpu = shouldUseCpuNoise; const use_cpu = shouldUseCpuNoise;
const { positivePrompt, negativePrompt } = getPresetModifiedPrompts(state);
/** /**
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the
* full graph here as a template. Then use the parameters from app state and set friendlier node * full graph here as a template. Then use the parameters from app state and set friendlier node

View File

@ -19,7 +19,7 @@ import {
POSITIVE_CONDITIONING, POSITIVE_CONDITIONING,
SEAMLESS, SEAMLESS,
} from 'features/nodes/util/graph/constants'; } from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate } from 'features/nodes/util/graph/graphBuilderUtils'; import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types'; import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph'; import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
@ -58,7 +58,6 @@ export const buildCanvasInpaintGraph = async (
canvasCoherenceEdgeSize, canvasCoherenceEdgeSize,
maskBlur, maskBlur,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
if (!model) { if (!model) {
log.error('No model found in state'); log.error('No model found in state');
@ -79,6 +78,8 @@ export const buildCanvasInpaintGraph = async (
const use_cpu = shouldUseCpuNoise; const use_cpu = shouldUseCpuNoise;
const { positivePrompt, negativePrompt } = getPresetModifiedPrompts(state);
const graph: NonNullableGraph = { const graph: NonNullableGraph = {
id: CANVAS_INPAINT_GRAPH, id: CANVAS_INPAINT_GRAPH,
nodes: { nodes: {

View File

@ -23,7 +23,7 @@ import {
POSITIVE_CONDITIONING, POSITIVE_CONDITIONING,
SEAMLESS, SEAMLESS,
} from 'features/nodes/util/graph/constants'; } from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate } from 'features/nodes/util/graph/graphBuilderUtils'; import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types'; import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph'; import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
@ -70,7 +70,6 @@ export const buildCanvasOutpaintGraph = async (
canvasCoherenceEdgeSize, canvasCoherenceEdgeSize,
maskBlur, maskBlur,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
if (!model) { if (!model) {
log.error('No model found in state'); log.error('No model found in state');
@ -91,6 +90,8 @@ export const buildCanvasOutpaintGraph = async (
const use_cpu = shouldUseCpuNoise; const use_cpu = shouldUseCpuNoise;
const { positivePrompt, negativePrompt } = getPresetModifiedPrompts(state);
const graph: NonNullableGraph = { const graph: NonNullableGraph = {
id: CANVAS_OUTPAINT_GRAPH, id: CANVAS_OUTPAINT_GRAPH,
nodes: { nodes: {

View File

@ -16,7 +16,7 @@ import {
SDXL_REFINER_SEAMLESS, SDXL_REFINER_SEAMLESS,
SEAMLESS, SEAMLESS,
} from 'features/nodes/util/graph/constants'; } from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate, getSDXLStylePrompts } from 'features/nodes/util/graph/graphBuilderUtils'; import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types'; import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types';
import { isNonRefinerMainModelConfig } from 'services/api/types'; import { isNonRefinerMainModelConfig } from 'services/api/types';
@ -51,7 +51,6 @@ export const buildCanvasSDXLImageToImageGraph = async (
seamlessYAxis, seamlessYAxis,
img2imgStrength: strength, img2imgStrength: strength,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;
@ -75,7 +74,7 @@ export const buildCanvasSDXLImageToImageGraph = async (
const use_cpu = shouldUseCpuNoise; const use_cpu = shouldUseCpuNoise;
// Construct Style Prompt // Construct Style Prompt
const { positiveStylePrompt, negativeStylePrompt } = getSDXLStylePrompts(state); const { positivePrompt, negativePrompt, positiveStylePrompt, negativeStylePrompt } = getPresetModifiedPrompts(state);
/** /**
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the

View File

@ -19,7 +19,7 @@ import {
SDXL_REFINER_SEAMLESS, SDXL_REFINER_SEAMLESS,
SEAMLESS, SEAMLESS,
} from 'features/nodes/util/graph/constants'; } from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate, getSDXLStylePrompts } from 'features/nodes/util/graph/graphBuilderUtils'; import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types'; import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph'; import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
@ -58,7 +58,6 @@ export const buildCanvasSDXLInpaintGraph = async (
canvasCoherenceEdgeSize, canvasCoherenceEdgeSize,
maskBlur, maskBlur,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;
@ -83,7 +82,7 @@ export const buildCanvasSDXLInpaintGraph = async (
const use_cpu = shouldUseCpuNoise; const use_cpu = shouldUseCpuNoise;
// Construct Style Prompt // Construct Style Prompt
const { positiveStylePrompt, negativeStylePrompt } = getSDXLStylePrompts(state); const { positivePrompt, negativePrompt, positiveStylePrompt, negativeStylePrompt } = getPresetModifiedPrompts(state);
const graph: NonNullableGraph = { const graph: NonNullableGraph = {
id: SDXL_CANVAS_INPAINT_GRAPH, id: SDXL_CANVAS_INPAINT_GRAPH,

View File

@ -23,7 +23,7 @@ import {
SDXL_REFINER_SEAMLESS, SDXL_REFINER_SEAMLESS,
SEAMLESS, SEAMLESS,
} from 'features/nodes/util/graph/constants'; } from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate, getSDXLStylePrompts } from 'features/nodes/util/graph/graphBuilderUtils'; import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types'; import type { ImageDTO, Invocation, NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph'; import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
@ -70,7 +70,6 @@ export const buildCanvasSDXLOutpaintGraph = async (
canvasCoherenceEdgeSize, canvasCoherenceEdgeSize,
maskBlur, maskBlur,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;
@ -94,7 +93,7 @@ export const buildCanvasSDXLOutpaintGraph = async (
const use_cpu = shouldUseCpuNoise; const use_cpu = shouldUseCpuNoise;
// Construct Style Prompt // Construct Style Prompt
const { positiveStylePrompt, negativeStylePrompt } = getSDXLStylePrompts(state); const { positivePrompt, negativePrompt, positiveStylePrompt, negativeStylePrompt } = getPresetModifiedPrompts(state);
const graph: NonNullableGraph = { const graph: NonNullableGraph = {
id: SDXL_CANVAS_OUTPAINT_GRAPH, id: SDXL_CANVAS_OUTPAINT_GRAPH,

View File

@ -14,7 +14,7 @@ import {
SDXL_REFINER_SEAMLESS, SDXL_REFINER_SEAMLESS,
SEAMLESS, SEAMLESS,
} from 'features/nodes/util/graph/constants'; } from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate, getSDXLStylePrompts } from 'features/nodes/util/graph/graphBuilderUtils'; import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import { isNonRefinerMainModelConfig, type NonNullableGraph } from 'services/api/types'; import { isNonRefinerMainModelConfig, type NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph'; import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
@ -44,7 +44,6 @@ export const buildCanvasSDXLTextToImageGraph = async (state: RootState): Promise
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
// The bounding box determines width and height, not the width and height params // The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions; const { width, height } = state.canvas.boundingBoxDimensions;
@ -67,7 +66,7 @@ export const buildCanvasSDXLTextToImageGraph = async (state: RootState): Promise
let modelLoaderNodeId = SDXL_MODEL_LOADER; let modelLoaderNodeId = SDXL_MODEL_LOADER;
// Construct Style Prompt // Construct Style Prompt
const { positiveStylePrompt, negativeStylePrompt } = getSDXLStylePrompts(state); const { positivePrompt, negativePrompt, positiveStylePrompt, negativeStylePrompt } = getPresetModifiedPrompts(state);
/** /**
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the

View File

@ -14,7 +14,7 @@ import {
POSITIVE_CONDITIONING, POSITIVE_CONDITIONING,
SEAMLESS, SEAMLESS,
} from 'features/nodes/util/graph/constants'; } from 'features/nodes/util/graph/constants';
import { getBoardField, getIsIntermediate } from 'features/nodes/util/graph/graphBuilderUtils'; import { getBoardField, getIsIntermediate, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import { isNonRefinerMainModelConfig, type NonNullableGraph } from 'services/api/types'; import { isNonRefinerMainModelConfig, type NonNullableGraph } from 'services/api/types';
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph'; import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
@ -44,7 +44,6 @@ export const buildCanvasTextToImageGraph = async (state: RootState): Promise<Non
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
// The bounding box determines width and height, not the width and height params // The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions; const { width, height } = state.canvas.boundingBoxDimensions;
@ -64,6 +63,8 @@ export const buildCanvasTextToImageGraph = async (state: RootState): Promise<Non
let modelLoaderNodeId = MAIN_MODEL_LOADER; let modelLoaderNodeId = MAIN_MODEL_LOADER;
const { positivePrompt, negativePrompt } = getPresetModifiedPrompts(state);
/** /**
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the
* full graph here as a template. Then use the parameters from app state and set friendlier node * full graph here as a template. Then use the parameters from app state and set friendlier node

View File

@ -22,7 +22,7 @@ import { addSeamless } from 'features/nodes/util/graph/generation/addSeamless';
import { addWatermarker } from 'features/nodes/util/graph/generation/addWatermarker'; import { addWatermarker } from 'features/nodes/util/graph/generation/addWatermarker';
import type { GraphType } from 'features/nodes/util/graph/generation/Graph'; import type { GraphType } from 'features/nodes/util/graph/generation/Graph';
import { Graph } from 'features/nodes/util/graph/generation/Graph'; import { Graph } from 'features/nodes/util/graph/generation/Graph';
import { getBoardField } from 'features/nodes/util/graph/graphBuilderUtils'; import { getBoardField, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import type { Invocation } from 'services/api/types'; import type { Invocation } from 'services/api/types';
import { isNonRefinerMainModelConfig } from 'services/api/types'; import { isNonRefinerMainModelConfig } from 'services/api/types';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -40,11 +40,12 @@ export const buildGenerationTabGraph = async (state: RootState): Promise<GraphTy
seed, seed,
vae, vae,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { width, height } = state.controlLayers.present.size; const { width, height } = state.controlLayers.present.size;
assert(model, 'No model found in state'); assert(model, 'No model found in state');
const { positivePrompt, negativePrompt } = getPresetModifiedPrompts(state);
const g = new Graph(CONTROL_LAYERS_GRAPH); const g = new Graph(CONTROL_LAYERS_GRAPH);
const modelLoader = g.addNode({ const modelLoader = g.addNode({
type: 'main_model_loader', type: 'main_model_loader',

View File

@ -19,7 +19,7 @@ import { addSDXLRefiner } from 'features/nodes/util/graph/generation/addSDXLRefi
import { addSeamless } from 'features/nodes/util/graph/generation/addSeamless'; import { addSeamless } from 'features/nodes/util/graph/generation/addSeamless';
import { addWatermarker } from 'features/nodes/util/graph/generation/addWatermarker'; import { addWatermarker } from 'features/nodes/util/graph/generation/addWatermarker';
import { Graph } from 'features/nodes/util/graph/generation/Graph'; import { Graph } from 'features/nodes/util/graph/generation/Graph';
import { getBoardField, getSDXLStylePrompts } from 'features/nodes/util/graph/graphBuilderUtils'; import { getBoardField, getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
import type { Invocation, NonNullableGraph } from 'services/api/types'; import type { Invocation, NonNullableGraph } from 'services/api/types';
import { isNonRefinerMainModelConfig } from 'services/api/types'; import { isNonRefinerMainModelConfig } from 'services/api/types';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -36,14 +36,13 @@ export const buildGenerationTabSDXLGraph = async (state: RootState): Promise<Non
vaePrecision, vaePrecision,
vae, vae,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.controlLayers.present;
const { width, height } = state.controlLayers.present.size; const { width, height } = state.controlLayers.present.size;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;
assert(model, 'No model found in state'); assert(model, 'No model found in state');
const { positiveStylePrompt, negativeStylePrompt } = getSDXLStylePrompts(state); const { positivePrompt, negativePrompt, positiveStylePrompt, negativeStylePrompt } = getPresetModifiedPrompts(state);
const g = new Graph(SDXL_CONTROL_LAYERS_GRAPH); const g = new Graph(SDXL_CONTROL_LAYERS_GRAPH);
const modelLoader = g.addNode({ const modelLoader = g.addNode({

View File

@ -1,5 +1,6 @@
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import type { BoardField } from 'features/nodes/types/common'; import type { BoardField } from 'features/nodes/types/common';
import { buildPresetModifiedPrompt } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
/** /**
@ -14,13 +15,30 @@ export const getBoardField = (state: RootState): BoardField | undefined => {
}; };
/** /**
* Gets the SDXL style prompts, based on the concat setting. * Gets the prompts, modified for the active style preset.
*/ */
export const getSDXLStylePrompts = (state: RootState): { positiveStylePrompt: string; negativeStylePrompt: string } => { export const getPresetModifiedPrompts = (state: RootState): { positivePrompt: string; negativePrompt: string, positiveStylePrompt?: string; negativeStylePrompt?: string } => {
const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } = const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } =
state.controlLayers.present; state.controlLayers.present;
const { activeStylePreset } = state.stylePreset
if (activeStylePreset) {
const presetModifiedPositivePrompt = buildPresetModifiedPrompt(activeStylePreset.preset_data.positive_prompt, positivePrompt)
const presetModifiedNegativePrompt = buildPresetModifiedPrompt(activeStylePreset.preset_data.negative_prompt, negativePrompt)
return { return {
positivePrompt: presetModifiedPositivePrompt,
negativePrompt: presetModifiedNegativePrompt,
positiveStylePrompt: shouldConcatPrompts ? presetModifiedPositivePrompt : positivePrompt2,
negativeStylePrompt: shouldConcatPrompts ? presetModifiedNegativePrompt : negativePrompt2,
};
}
return {
positivePrompt,
negativePrompt,
positiveStylePrompt: shouldConcatPrompts ? positivePrompt : positivePrompt2, positiveStylePrompt: shouldConcatPrompts ? positivePrompt : positivePrompt2,
negativeStylePrompt: shouldConcatPrompts ? negativePrompt : negativePrompt2, negativeStylePrompt: shouldConcatPrompts ? negativePrompt : negativePrompt2,
}; };

View File

@ -7,7 +7,7 @@ import { ParamPositivePrompt } from 'features/parameters/components/Core/ParamPo
import { selectGenerationSlice } from 'features/parameters/store/generationSlice'; import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
import { ParamSDXLNegativeStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt'; import { ParamSDXLNegativeStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt';
import { ParamSDXLPositiveStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt'; import { ParamSDXLPositiveStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt';
import { StylePresetMenuTrigger } from 'features/stylePresets/components/StylePresetMenuTrigger'; import { usePresetModifiedPrompts } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
import { memo } from 'react'; import { memo } from 'react';
const concatPromptsSelector = createSelector( const concatPromptsSelector = createSelector(
@ -19,16 +19,14 @@ const concatPromptsSelector = createSelector(
export const Prompts = memo(() => { export const Prompts = memo(() => {
const shouldConcatPrompts = useAppSelector(concatPromptsSelector); const shouldConcatPrompts = useAppSelector(concatPromptsSelector);
const calculatedPosPrompt = useAppSelector((s) => s.stylePreset.calculatedPosPrompt); const { presetModifiedPositivePrompt, presetModifiedNegativePrompt } = usePresetModifiedPrompts();
const calculatedNegPrompt = useAppSelector((s) => s.stylePreset.calculatedNegPrompt);
return ( return (
<Flex flexDir="column" gap={2}> <Flex flexDir="column" gap={2}>
<StylePresetMenuTrigger />
<ParamPositivePrompt /> <ParamPositivePrompt />
<Flex>{calculatedPosPrompt}</Flex> <Flex>{presetModifiedPositivePrompt}</Flex>
{!shouldConcatPrompts && <ParamSDXLPositiveStylePrompt />} {!shouldConcatPrompts && <ParamSDXLPositiveStylePrompt />}
<ParamNegativePrompt /> <ParamNegativePrompt />
<Flex>{calculatedNegPrompt}</Flex> <Flex>{presetModifiedNegativePrompt}</Flex>
{!shouldConcatPrompts && <ParamSDXLNegativeStylePrompt />} {!shouldConcatPrompts && <ParamSDXLNegativeStylePrompt />}
</Flex> </Flex>
); );

View File

@ -0,0 +1,72 @@
import { Flex, IconButton, Text } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { negativePromptChanged, positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
import ModelImage from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelImage';
import { usePresetModifiedPrompts } from 'features/stylePresets/hooks/usePresetModifiedPrompts';
import { activeStylePresetChanged } from 'features/stylePresets/store/stylePresetSlice';
import type { MouseEventHandler} from 'react';
import { useCallback } from 'react';
import { CgPushDown } from 'react-icons/cg';
import { PiXBold } from 'react-icons/pi';
export const ActiveStylePreset = () => {
const { activeStylePreset } = useAppSelector((s) => s.stylePreset);
const dispatch = useAppDispatch();
const { presetModifiedPositivePrompt, presetModifiedNegativePrompt } = usePresetModifiedPrompts();
const handleClearActiveStylePreset = useCallback<MouseEventHandler<HTMLButtonElement>>(
(e) => {
e.stopPropagation();
dispatch(activeStylePresetChanged(null));
},
[dispatch]
);
const handleFlattenPrompts = useCallback<MouseEventHandler<HTMLButtonElement>>(
(e) => {
e.stopPropagation();
dispatch(positivePromptChanged(presetModifiedPositivePrompt));
dispatch(negativePromptChanged(presetModifiedNegativePrompt));
dispatch(activeStylePresetChanged(null));
},
[dispatch, presetModifiedPositivePrompt, presetModifiedNegativePrompt]
);
if (!activeStylePreset) {
return <>Choose Preset</>;
}
return (
<>
<Flex justifyContent="space-between" w="full" alignItems="center">
<Flex gap="2">
<ModelImage image_url={null} />
<Flex flexDir="column">
<Text variant="subtext" fontSize="xs">
Prompt Style
</Text>
<Text fontSize="md" fontWeight="semibold">
{activeStylePreset.name}
</Text>
</Flex>
</Flex>
<Flex gap="1">
<IconButton
onClick={handleFlattenPrompts}
variant="ghost"
size="md"
aria-label="Flatten"
icon={<CgPushDown />}
/>
<IconButton
onClick={handleClearActiveStylePreset}
variant="ghost"
size="md"
aria-label="Clear"
icon={<PiXBold />}
/>
</Flex>
</Flex>
</>
);
};

View File

@ -1,56 +1,49 @@
import { Button, Flex, FormControl, FormLabel, Input, Textarea } from '@invoke-ai/ui-library'; import { Button, Flex, FormControl, FormLabel, Icon, Input, Text } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useStylePresetFields } from 'features/stylePresets/hooks/useStylePresetFields';
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice'; import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
import { toast } from 'features/toast/toast'; import { toast } from 'features/toast/toast';
import type { ChangeEventHandler } from 'react'; import { useCallback } from 'react';
import { useCallback, useEffect, useState } from 'react'; import type { SubmitHandler} from 'react-hook-form';
import { useForm } from 'react-hook-form';
import { PiBracketsCurlyBold } from 'react-icons/pi';
import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets'; import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
import { useCreateStylePresetMutation, useUpdateStylePresetMutation } from 'services/api/endpoints/stylePresets'; import { useCreateStylePresetMutation, useUpdateStylePresetMutation } from 'services/api/endpoints/stylePresets';
import { StylePresetPromptField } from './StylePresetPromptField';
export type StylePresetFormData = {
name: string;
positivePrompt: string;
negativePrompt: string;
};
export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePresetRecordDTO | null }) => { export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePresetRecordDTO | null }) => {
const [createStylePreset] = useCreateStylePresetMutation(); const [createStylePreset] = useCreateStylePresetMutation();
const [updateStylePreset] = useUpdateStylePresetMutation(); const [updateStylePreset] = useUpdateStylePresetMutation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [name, setName] = useState(updatingPreset ? updatingPreset.name : ''); const stylePresetFieldDefaults = useStylePresetFields(updatingPreset);
const [posPrompt, setPosPrompt] = useState(updatingPreset ? updatingPreset.preset_data.positive_prompt : '');
const [negPrompt, setNegPrompt] = useState(updatingPreset ? updatingPreset.preset_data.negative_prompt : '');
const handleChangeName = useCallback<ChangeEventHandler<HTMLInputElement>>((e) => { const { handleSubmit, control, formState, reset, register } = useForm<StylePresetFormData>({
setName(e.target.value); defaultValues: stylePresetFieldDefaults,
}, []); });
const handleChangePosPrompt = useCallback<ChangeEventHandler<HTMLTextAreaElement>>((e) => { const handleClickSave = useCallback<SubmitHandler<StylePresetFormData>>(
setPosPrompt(e.target.value); async (data) => {
}, []);
const handleChangeNegPrompt = useCallback<ChangeEventHandler<HTMLTextAreaElement>>((e) => {
setNegPrompt(e.target.value);
}, []);
useEffect(() => {
if (updatingPreset) {
setName(updatingPreset.name);
setPosPrompt(updatingPreset.preset_data.positive_prompt);
setNegPrompt(updatingPreset.preset_data.negative_prompt);
} else {
setName('');
setPosPrompt('');
setNegPrompt('');
}
}, [updatingPreset]);
const handleClickSave = useCallback(async () => {
try { try {
if (updatingPreset) { if (updatingPreset) {
await updateStylePreset({ await updateStylePreset({
id: updatingPreset.id, id: updatingPreset.id,
changes: { name, preset_data: { positive_prompt: posPrompt, negative_prompt: negPrompt } }, changes: {
name: data.name,
preset_data: { positive_prompt: data.positivePrompt, negative_prompt: data.negativePrompt },
},
}).unwrap(); }).unwrap();
} else { } else {
await createStylePreset({ await createStylePreset({
name: name, name: data.name,
preset_data: { positive_prompt: posPrompt, negative_prompt: negPrompt }, preset_data: { positive_prompt: data.positivePrompt, negative_prompt: data.negativePrompt },
}).unwrap(); }).unwrap();
} }
} catch (error) { } catch (error) {
@ -62,23 +55,26 @@ export const StylePresetForm = ({ updatingPreset }: { updatingPreset: StylePrese
dispatch(updatingStylePresetChanged(null)); dispatch(updatingStylePresetChanged(null));
dispatch(isModalOpenChanged(false)); dispatch(isModalOpenChanged(false));
}, [dispatch, updatingPreset, name, posPrompt, negPrompt, updateStylePreset, createStylePreset]); },
[dispatch, updatingPreset, updateStylePreset, createStylePreset]
);
return ( return (
<Flex flexDir="column" gap="4"> <Flex flexDir="column" gap="4">
<FormControl> <FormControl>
<FormLabel>Name</FormLabel> <FormLabel>Name</FormLabel>
<Input value={name} onChange={handleChangeName} /> <Input size="md" {...register('name')} />
</FormControl> </FormControl>
<FormControl> <Flex flexDir="column" bgColor="base.750" borderRadius="base" padding="10px" gap="10px">
<FormLabel>Positive Prompt</FormLabel> <Text variant="subtext">
<Textarea value={posPrompt} onChange={handleChangePosPrompt} /> Use the <Icon as={PiBracketsCurlyBold} /> button to specify where your manual prompt should be included in the
</FormControl> template. If you do not provide one, the template will be appended to your prompt.
<FormControl> </Text>
<FormLabel>Negative Prompt</FormLabel> <StylePresetPromptField label="Positive Prompt" control={control} name="positivePrompt" />
<Textarea value={negPrompt} onChange={handleChangeNegPrompt} /> <StylePresetPromptField label="Negative Prompt" control={control} name="negativePrompt" />
</FormControl> </Flex>
<Button onClick={handleClickSave}>Save</Button>
<Button onClick={handleSubmit(handleClickSave)}>Save</Button>
</Flex> </Flex>
); );
}; };

View File

@ -0,0 +1,31 @@
import { Button, Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library';
import { PiCaretDownBold } from 'react-icons/pi';
import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
import { StylePresetListItem } from './StylePresetListItem';
export const StylePresetList = ({ title, data }: { title: string; data: StylePresetRecordDTO[] }) => {
const { onToggle, isOpen } = useDisclosure({ defaultIsOpen: true });
if (!data.length) {
return <></>;
}
return (
<Flex flexDir="column">
<Button variant="unstyled" onClick={onToggle}>
<Flex gap="2" alignItems="center">
<Icon boxSize={4} as={PiCaretDownBold} transform={isOpen ? undefined : 'rotate(-90deg)'} fill="base.500" />
<Text fontSize="sm" fontWeight="semibold" userSelect="none" color="base.500">
{title}
</Text>
</Flex>
</Button>
<Collapse in={isOpen}>
{data.map((preset) => (
<StylePresetListItem preset={preset} key={preset.id} />
))}
</Collapse>
</Flex>
);
};

View File

@ -1,14 +1,17 @@
import { Button, Flex, Text } from '@invoke-ai/ui-library'; import { Badge, Flex, IconButton, Text } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import ModelImage from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelImage';
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice'; import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
import { activeStylePresetChanged, isMenuOpenChanged } from 'features/stylePresets/store/stylePresetSlice';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { PiPencilBold, PiTrashBold } from 'react-icons/pi';
import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets'; import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
import { useDeleteStylePresetMutation } from 'services/api/endpoints/stylePresets'; import { useDeleteStylePresetMutation } from 'services/api/endpoints/stylePresets';
import { activeStylePresetChanged, isMenuOpenChanged } from '../store/stylePresetSlice';
export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordDTO }) => { export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordDTO }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [deleteStylePreset] = useDeleteStylePresetMutation(); const [deleteStylePreset] = useDeleteStylePresetMutation();
const activeStylePreset = useAppSelector((s) => s.stylePreset.activeStylePreset);
const handleClickEdit = useCallback(() => { const handleClickEdit = useCallback(() => {
dispatch(updatingStylePresetChanged(preset)); dispatch(updatingStylePresetChanged(preset));
@ -27,10 +30,47 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordDTO }
}, [preset]); }, [preset]);
return ( return (
<> <Flex
<Flex flexDir="column" gap="2"> gap="4"
onClick={handleClickApply}
cursor="pointer"
_hover={{ backgroundColor: 'base.750' }}
padding="10px"
borderRadius="base"
alignItems="center"
w="full"
>
<ModelImage image_url={null} />
<Flex flexDir="column" w="full">
<Flex w="full" justifyContent="space-between">
<Flex alignItems="center" gap="2">
<Text fontSize="md">{preset.name}</Text> <Text fontSize="md">{preset.name}</Text>
<Flex flexDir="column" layerStyle="third" borderRadius="base" padding="10px"> {activeStylePreset && activeStylePreset.id === preset.id && (
<Badge
color="invokeBlue.400"
borderColor="invokeBlue.700"
borderWidth={1}
bg="transparent"
flexShrink={0}
>
Active
</Badge>
)}
</Flex>
<Flex alignItems="center" gap="1">
<IconButton size="sm" variant="ghost" aria-label="Edit" onClick={handleClickEdit} icon={<PiPencilBold />} />
<IconButton
size="sm"
variant="ghost"
aria-label="Delete"
onClick={handleDeletePreset}
icon={<PiTrashBold />}
/>
</Flex>
</Flex>
<Flex flexDir="column">
<Text fontSize="xs"> <Text fontSize="xs">
<Text as="span" fontWeight="semibold"> <Text as="span" fontWeight="semibold">
Positive prompt: Positive prompt:
@ -43,11 +83,8 @@ export const StylePresetListItem = ({ preset }: { preset: StylePresetRecordDTO }
</Text>{' '} </Text>{' '}
{preset.preset_data.negative_prompt} {preset.preset_data.negative_prompt}
</Text> </Text>
<Button onClick={handleClickEdit}>Edit</Button>
<Button onClick={handleDeletePreset}>Delete</Button>
<Button onClick={handleClickApply}>Apply</Button>
</Flex> </Flex>
</Flex> </Flex>
</> </Flex>
); );
}; };

View File

@ -1,13 +1,42 @@
import { Button, Flex, Text } from '@invoke-ai/ui-library'; import { Flex, IconButton, Text } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { EMPTY_ARRAY } from 'app/store/constants';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice'; import { isModalOpenChanged, updatingStylePresetChanged } from 'features/stylePresets/store/stylePresetModalSlice';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { PiPlusBold } from 'react-icons/pi';
import type { StylePresetRecordDTO} from 'services/api/endpoints/stylePresets';
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets'; import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
import { StylePresetListItem } from './StylePresetListItem'; import { StylePresetList } from './StylePresetList';
import StylePresetSearch from './StylePresetSearch';
export const StylePresetMenu = () => { export const StylePresetMenu = () => {
const { data } = useListStylePresetsQuery({}); const searchTerm = useAppSelector((s) => s.stylePreset.searchTerm);
const { data } = useListStylePresetsQuery(undefined, {
selectFromResult: ({ data, error, isLoading }) => {
const filteredData =
data?.filter((preset) => preset.name.toLowerCase().includes(searchTerm.toLowerCase())) || EMPTY_ARRAY;
const groupedData = filteredData.reduce(
(acc: { defaultPresets: StylePresetRecordDTO[]; presets: StylePresetRecordDTO[] }, preset) => {
if (preset.is_default) {
acc.defaultPresets.push(preset);
} else {
acc.presets.push(preset);
}
return acc;
},
{ defaultPresets: [], presets: [] }
);
return {
data: groupedData,
error,
isLoading,
};
},
});
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const handleClickAddNew = useCallback(() => { const handleClickAddNew = useCallback(() => {
@ -16,19 +45,30 @@ export const StylePresetMenu = () => {
}, [dispatch]); }, [dispatch]);
return ( return (
<> <Flex flexDir="column" gap="2" padding="10px">
<Flex flexDir="column" gap="2">
<Flex alignItems="center" gap="10" w="full" justifyContent="space-between"> <Flex alignItems="center" gap="10" w="full" justifyContent="space-between">
<Text fontSize="sm" fontWeight="semibold" userSelect="none" color="base.500"> <StylePresetSearch />
Style Presets <IconButton
</Text> icon={<PiPlusBold />}
<Button size="sm" onClick={handleClickAddNew}> tooltip="Create Preset"
Add New aria-label="Create Preset"
</Button> onClick={handleClickAddNew}
size="md"
variant="link"
w={8}
h={8}
/>
</Flex> </Flex>
{data?.items.map((preset) => <StylePresetListItem preset={preset} key={preset.id} />)} {data.presets.length === 0 && data.defaultPresets.length === 0 && (
<Text m="20px" textAlign="center">
No matching presets
</Text>
)}
<StylePresetList title="My Presets" data={data.presets} />
<StylePresetList title="Default Presets" data={data.defaultPresets} />
</Flex> </Flex>
</>
); );
}; };

View File

@ -1,34 +1,31 @@
import { Button, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library'; import { Flex, Icon } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { StylePresetMenu } from './StylePresetMenu'; import { isMenuOpenChanged } from 'features/stylePresets/store/stylePresetSlice';
import { useAppDispatch, useAppSelector } from '../../../app/store/storeHooks';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { isMenuOpenChanged } from '../store/stylePresetSlice'; import { PiCaretDownBold } from 'react-icons/pi';
import { ActiveStylePreset } from './ActiveStylePreset';
export const StylePresetMenuTrigger = () => { export const StylePresetMenuTrigger = () => {
const isMenuOpen = useAppSelector((s) => s.stylePreset.isMenuOpen); const isMenuOpen = useAppSelector((s) => s.stylePreset.isMenuOpen);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const handleClose = useCallback(() => {
dispatch(isMenuOpenChanged(false));
}, [dispatch]);
const handleToggle = useCallback(() => { const handleToggle = useCallback(() => {
dispatch(isMenuOpenChanged(!isMenuOpen)); dispatch(isMenuOpenChanged(!isMenuOpen));
}, [dispatch, isMenuOpen]); }, [dispatch, isMenuOpen]);
return ( return (
<Popover isOpen={isMenuOpen} onClose={handleClose}> <Flex
<PopoverTrigger> onClick={handleToggle}
<Button size="sm" onClick={handleToggle}> backgroundColor="base.800"
Style Presets justifyContent="space-between"
</Button> alignItems="center"
</PopoverTrigger> padding="5px 10px"
<PopoverContent> borderRadius="base"
<PopoverBody> >
<StylePresetMenu /> <ActiveStylePreset />
</PopoverBody>
</PopoverContent> <Icon as={PiCaretDownBold} />
</Popover> </Flex>
); );
}; };

View File

@ -0,0 +1,61 @@
import { Flex, FormControl, FormLabel, IconButton, Textarea } from '@invoke-ai/ui-library';
import type { ChangeEventHandler } from 'react';
import { useCallback, useMemo, useRef } from 'react';
import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { PiBracketsCurlyBold } from 'react-icons/pi';
import type { StylePresetFormData } from './StylePresetForm';
import { PRESET_PLACEHOLDER } from '../hooks/usePresetModifiedPrompts';
interface Props extends UseControllerProps<StylePresetFormData> {
label: string;
}
export const StylePresetPromptField = (props: Props) => {
const { field } = useController(props);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const onChange = useCallback<ChangeEventHandler<HTMLTextAreaElement>>(
(v) => {
field.onChange(v.target.value);
},
[field]
);
const value = useMemo(() => {
return field.value;
}, [field.value]);
const insertPromptPlaceholder = useCallback(() => {
if (textareaRef.current) {
const cursorPos = textareaRef.current.selectionStart;
const textBeforeCursor = value.slice(0, cursorPos);
const textAfterCursor = value.slice(cursorPos);
const newValue = textBeforeCursor + PRESET_PLACEHOLDER + textAfterCursor;
field.onChange(newValue);
} else {
field.onChange(value + PRESET_PLACEHOLDER);
}
}, [value, field, textareaRef]);
const isPromptPresent = useMemo(() => value.includes(PRESET_PLACEHOLDER), [value]);
return (
<FormControl orientation="vertical">
<Flex alignItems="center" gap="1">
<FormLabel>{props.label}</FormLabel>
<IconButton
onClick={insertPromptPlaceholder}
size="sm"
icon={<PiBracketsCurlyBold />}
aria-label="Insert placeholder"
isDisabled={isPromptPresent}
/>
</Flex>
<Textarea size="sm" ref={textareaRef} value={value} onChange={onChange} />
</FormControl>
);
};

View File

@ -0,0 +1,60 @@
import { IconButton, Input, InputGroup, InputRightElement } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { searchTermChanged } from 'features/stylePresets/store/stylePresetSlice';
import type { ChangeEvent, KeyboardEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiXBold } from 'react-icons/pi';
const StylePresetSearch = () => {
const dispatch = useAppDispatch();
const searchTerm = useAppSelector((s) => s.stylePreset.searchTerm);
const { t } = useTranslation();
const handlePresetSearch = useCallback(
(newSearchTerm: string) => {
dispatch(searchTermChanged(newSearchTerm));
},
[dispatch]
);
const clearPresetSearch = useCallback(() => {
dispatch(searchTermChanged(''));
}, [dispatch]);
const handleKeydown = useCallback(
(e: KeyboardEvent<HTMLInputElement>) => {
// exit search mode on escape
if (e.key === 'Escape') {
clearPresetSearch();
}
},
[clearPresetSearch]
);
const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
handlePresetSearch(e.target.value);
},
[handlePresetSearch]
);
return (
<InputGroup>
<Input placeholder="Search by name" value={searchTerm} onKeyDown={handleKeydown} onChange={handleChange} />
{searchTerm && searchTerm.length && (
<InputRightElement h="full" pe={2}>
<IconButton
onClick={clearPresetSearch}
size="sm"
variant="link"
aria-label={t('boards.clearSearch')}
icon={<PiXBold />}
/>
</InputRightElement>
)}
</InputGroup>
);
};
export default memo(StylePresetSearch);

View File

@ -0,0 +1,25 @@
import { useAppSelector } from "app/store/storeHooks"
export const PRESET_PLACEHOLDER = `{prompt}`
export const buildPresetModifiedPrompt = (presetPrompt: string, currentPrompt: string) => {
return presetPrompt.includes(PRESET_PLACEHOLDER) ? presetPrompt.replace(new RegExp(PRESET_PLACEHOLDER), currentPrompt) : `${currentPrompt} ${presetPrompt}`
}
export const usePresetModifiedPrompts = () => {
const activeStylePreset = useAppSelector(s => s.stylePreset.activeStylePreset)
const { positivePrompt, negativePrompt } = useAppSelector(s => s.controlLayers.present)
if (!activeStylePreset) {
return { presetModifiedPositivePrompt: positivePrompt, presetModifiedNegativePrompt: negativePrompt }
}
const { positive_prompt: presetPositivePrompt, negative_prompt: presetNegativePrompt } = activeStylePreset.preset_data;
const presetModifiedPositivePrompt = buildPresetModifiedPrompt(presetPositivePrompt, positivePrompt)
const presetModifiedNegativePrompt = buildPresetModifiedPrompt(presetNegativePrompt, negativePrompt)
return { presetModifiedPositivePrompt, presetModifiedNegativePrompt }
}

View File

@ -0,0 +1,17 @@
import { useMemo } from 'react';
import type { StylePresetRecordDTO } from 'services/api/endpoints/stylePresets';
export const useStylePresetFields = (preset: StylePresetRecordDTO | null) => {
const stylePresetFieldDefaults = useMemo(() => {
return {
name: preset ? preset.name : '',
positivePrompt: preset ? preset.preset_data.positive_prompt : '',
negativePrompt: preset ? preset.preset_data.negative_prompt : ''
};
}, [
preset
]);
return stylePresetFieldDefaults;
};

View File

@ -9,8 +9,7 @@ import type { StylePresetState } from './types';
export const initialState: StylePresetState = { export const initialState: StylePresetState = {
isMenuOpen: false, isMenuOpen: false,
activeStylePreset: null, activeStylePreset: null,
calculatedPosPrompt: undefined, searchTerm: ""
calculatedNegPrompt: undefined
}; };
@ -24,15 +23,12 @@ export const stylePresetSlice = createSlice({
activeStylePresetChanged: (state, action: PayloadAction<StylePresetRecordDTO | null>) => { activeStylePresetChanged: (state, action: PayloadAction<StylePresetRecordDTO | null>) => {
state.activeStylePreset = action.payload; state.activeStylePreset = action.payload;
}, },
calculatedPosPromptChanged: (state, action: PayloadAction<string | undefined>) => { searchTermChanged: (state, action: PayloadAction<string>) => {
state.calculatedPosPrompt = action.payload; state.searchTerm = action.payload;
},
calculatedNegPromptChanged: (state, action: PayloadAction<string | undefined>) => {
state.calculatedNegPrompt = action.payload;
}, },
}, },
}); });
export const { isMenuOpenChanged, activeStylePresetChanged, calculatedPosPromptChanged, calculatedNegPromptChanged } = stylePresetSlice.actions; export const { isMenuOpenChanged, activeStylePresetChanged, searchTermChanged } = stylePresetSlice.actions;
export const selectStylePresetSlice = (state: RootState) => state.stylePreset; export const selectStylePresetSlice = (state: RootState) => state.stylePreset;

View File

@ -8,7 +8,6 @@ export type StylePresetModalState = {
export type StylePresetState = { export type StylePresetState = {
isMenuOpen: boolean; isMenuOpen: boolean;
activeStylePreset: StylePresetRecordDTO | null; activeStylePreset: StylePresetRecordDTO | null;
calculatedPosPrompt?: string searchTerm: string
calculatedNegPrompt?: string
} }

View File

@ -1,5 +1,5 @@
import type { ChakraProps } from '@invoke-ai/ui-library'; import type { ChakraProps } from '@invoke-ai/ui-library';
import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; import { Box, Flex, Portal,Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent'; import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent';
@ -13,10 +13,12 @@ import { ControlSettingsAccordion } from 'features/settingsAccordions/components
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion'; import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
import { ImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion'; import { ImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion';
import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion'; import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion';
import { StylePresetMenu } from 'features/stylePresets/components/StylePresetMenu';
import { StylePresetMenuTrigger } from 'features/stylePresets/components/StylePresetMenuTrigger';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const overlayScrollbarsStyles: CSSProperties = { const overlayScrollbarsStyles: CSSProperties = {
@ -58,11 +60,31 @@ const ParametersPanelTextToImage = () => {
[dispatch] [dispatch]
); );
const ref = useRef<HTMLDivElement>(null);
const isMenuOpen = useAppSelector((s) => s.stylePreset.isMenuOpen);
return ( return (
<Flex w="full" h="full" flexDir="column" gap={2}> <Flex w="full" h="full" flexDir="column" gap={2}>
<QueueControls /> <QueueControls />
<StylePresetMenuTrigger />
<Flex w="full" h="full" position="relative"> <Flex w="full" h="full" position="relative">
<Box position="absolute" top={0} left={0} right={0} bottom={0} ref={ref}>
<Portal containerRef={ref}>
{isMenuOpen && (
<Box position="absolute" top={0} left={0} right={0} bottom={0}> <Box position="absolute" top={0} left={0} right={0} bottom={0}>
<OverlayScrollbarsComponent
defer
style={overlayScrollbarsStyles}
options={overlayScrollbarsParams.options}
// backgroundColor="rgba(0,0,0,0.5)"
>
<Flex gap={2} flexDirection="column" h="full" w="full" layerStyle="second">
<StylePresetMenu />
</Flex>
</OverlayScrollbarsComponent>
</Box>
)}
</Portal>
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}> <OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
<Flex gap={2} flexDirection="column" h="full" w="full"> <Flex gap={2} flexDirection="column" h="full" w="full">
<Prompts /> <Prompts />

View File

@ -67,11 +67,10 @@ export const stylePresetsApi = api.injectEndpoints({
}), }),
listStylePresets: build.query< listStylePresets: build.query<
paths['/api/v1/style_presets/']['get']['responses']['200']['content']['application/json'], paths['/api/v1/style_presets/']['get']['responses']['200']['content']['application/json'],
NonNullable<paths['/api/v1/style_presets/']['get']['parameters']['query']> void
>({ >({
query: (params) => ({ query: () => ({
url: buildStylePresetsUrl(), url: buildStylePresetsUrl(),
params,
}), }),
providesTags: ['FetchOnReconnect', { type: 'StylePreset', id: LIST_TAG }], providesTags: ['FetchOnReconnect', { type: 'StylePreset', id: LIST_TAG }],
}), }),

View File

@ -7345,147 +7345,147 @@ export type components = {
project_id: string | null; project_id: string | null;
}; };
InvocationOutputMap: { InvocationOutputMap: {
crop_latents: components["schemas"]["LatentsOutput"];
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
color_map_image_processor: components["schemas"]["ImageOutput"];
img_paste: components["schemas"]["ImageOutput"];
image_collection: components["schemas"]["ImageCollectionOutput"];
img_channel_offset: components["schemas"]["ImageOutput"];
compel: components["schemas"]["ConditioningOutput"];
denoise_latents: components["schemas"]["LatentsOutput"];
t2i_adapter: components["schemas"]["T2IAdapterOutput"];
pair_tile_image: components["schemas"]["PairTileImageOutput"];
invert_tensor_mask: components["schemas"]["MaskOutput"];
img_conv: components["schemas"]["ImageOutput"];
img_hue_adjust: components["schemas"]["ImageOutput"];
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
conditioning: components["schemas"]["ConditioningOutput"];
mlsd_image_processor: components["schemas"]["ImageOutput"];
image_mask_to_tensor: components["schemas"]["MaskOutput"];
float_to_int: components["schemas"]["IntegerOutput"];
img_scale: components["schemas"]["ImageOutput"];
color: components["schemas"]["ColorOutput"];
midas_depth_image_processor: components["schemas"]["ImageOutput"];
mul: components["schemas"]["IntegerOutput"];
pidi_image_processor: components["schemas"]["ImageOutput"];
img_crop: components["schemas"]["ImageOutput"];
metadata_item: components["schemas"]["MetadataItemOutput"];
sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"];
metadata: components["schemas"]["MetadataOutput"];
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
lblend: components["schemas"]["LatentsOutput"];
canvas_paste_back: components["schemas"]["ImageOutput"];
lresize: components["schemas"]["LatentsOutput"];
sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"];
normalbae_image_processor: components["schemas"]["ImageOutput"];
random_range: components["schemas"]["IntegerCollectionOutput"];
img_chan: components["schemas"]["ImageOutput"];
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
img_nsfw: components["schemas"]["ImageOutput"];
mask_combine: components["schemas"]["ImageOutput"];
color_correct: components["schemas"]["ImageOutput"];
latents: components["schemas"]["LatentsOutput"];
seamless: components["schemas"]["SeamlessModeOutput"];
lscale: components["schemas"]["LatentsOutput"];
string_join_three: components["schemas"]["StringOutput"];
lineart_image_processor: components["schemas"]["ImageOutput"];
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
face_mask_detection: components["schemas"]["FaceMaskOutput"];
esrgan: components["schemas"]["ImageOutput"]; esrgan: components["schemas"]["ImageOutput"];
mask_edge: components["schemas"]["ImageOutput"];
segment_anything_processor: components["schemas"]["ImageOutput"];
cv_inpaint: components["schemas"]["ImageOutput"];
tomask: components["schemas"]["ImageOutput"];
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
infill_cv2: components["schemas"]["ImageOutput"];
spandrel_image_to_image: components["schemas"]["ImageOutput"];
conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
img_resize: components["schemas"]["ImageOutput"];
create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
controlnet: components["schemas"]["ControlOutput"];
merge_tiles_to_image: components["schemas"]["ImageOutput"];
mediapipe_face_processor: components["schemas"]["ImageOutput"];
img_mul: components["schemas"]["ImageOutput"];
image: components["schemas"]["ImageOutput"];
merge_metadata: components["schemas"]["MetadataOutput"];
add: components["schemas"]["IntegerOutput"];
mask_from_id: components["schemas"]["ImageOutput"];
vae_loader: components["schemas"]["VAEOutput"];
ideal_size: components["schemas"]["IdealSizeOutput"];
noise: components["schemas"]["NoiseOutput"];
sub: components["schemas"]["IntegerOutput"];
freeu: components["schemas"]["UNetOutput"];
lora_selector: components["schemas"]["LoRASelectorOutput"];
integer: components["schemas"]["IntegerOutput"];
l2i: components["schemas"]["ImageOutput"];
integer_collection: components["schemas"]["IntegerCollectionOutput"];
rand_float: components["schemas"]["FloatOutput"];
iterate: components["schemas"]["IterateInvocationOutput"];
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
hed_image_processor: components["schemas"]["ImageOutput"];
infill_lama: components["schemas"]["ImageOutput"];
boolean: components["schemas"]["BooleanOutput"];
float: components["schemas"]["FloatOutput"];
range_of_size: components["schemas"]["IntegerCollectionOutput"];
float_collection: components["schemas"]["FloatCollectionOutput"];
img_channel_multiply: components["schemas"]["ImageOutput"];
prompt_from_file: components["schemas"]["StringCollectionOutput"];
infill_rgba: components["schemas"]["ImageOutput"];
rectangle_mask: components["schemas"]["MaskOutput"];
depth_anything_image_processor: components["schemas"]["ImageOutput"];
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
rand_int: components["schemas"]["IntegerOutput"];
latents_collection: components["schemas"]["LatentsCollectionOutput"];
string_split: components["schemas"]["String2Output"];
round_float: components["schemas"]["FloatOutput"];
canny_image_processor: components["schemas"]["ImageOutput"];
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
float_range: components["schemas"]["FloatCollectionOutput"];
img_ilerp: components["schemas"]["ImageOutput"];
unsharp_mask: components["schemas"]["ImageOutput"];
img_watermark: components["schemas"]["ImageOutput"];
model_identifier: components["schemas"]["ModelIdentifierOutput"];
string_split_neg: components["schemas"]["StringPosNegOutput"];
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
collect: components["schemas"]["CollectInvocationOutput"];
core_metadata: components["schemas"]["MetadataOutput"];
img_pad_crop: components["schemas"]["ImageOutput"];
i2l: components["schemas"]["LatentsOutput"];
tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"]; tiled_multi_diffusion_denoise_latents: components["schemas"]["LatentsOutput"];
save_image: components["schemas"]["ImageOutput"]; conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
show_image: components["schemas"]["ImageOutput"]; merge_metadata: components["schemas"]["MetadataOutput"];
string_collection: components["schemas"]["StringCollectionOutput"]; lresize: components["schemas"]["LatentsOutput"];
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"]; string_split_neg: components["schemas"]["StringPosNegOutput"];
infill_patchmatch: components["schemas"]["ImageOutput"]; img_channel_multiply: components["schemas"]["ImageOutput"];
main_model_loader: components["schemas"]["ModelLoaderOutput"]; add: components["schemas"]["IntegerOutput"];
lora_loader: components["schemas"]["LoRALoaderOutput"]; lscale: components["schemas"]["LatentsOutput"];
string: components["schemas"]["StringOutput"];
heuristic_resize: components["schemas"]["ImageOutput"];
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
div: components["schemas"]["IntegerOutput"];
blank_image: components["schemas"]["ImageOutput"];
float_math: components["schemas"]["FloatOutput"];
infill_tile: components["schemas"]["ImageOutput"];
sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"];
face_off: components["schemas"]["FaceOffOutput"];
tile_image_processor: components["schemas"]["ImageOutput"];
leres_image_processor: components["schemas"]["ImageOutput"];
face_identifier: components["schemas"]["ImageOutput"];
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
scheduler: components["schemas"]["SchedulerOutput"];
img_lerp: components["schemas"]["ImageOutput"];
string_replace: components["schemas"]["StringOutput"]; string_replace: components["schemas"]["StringOutput"];
step_param_easing: components["schemas"]["FloatCollectionOutput"]; sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"];
collect: components["schemas"]["CollectInvocationOutput"];
face_off: components["schemas"]["FaceOffOutput"];
ideal_size: components["schemas"]["IdealSizeOutput"];
float_to_int: components["schemas"]["IntegerOutput"];
mlsd_image_processor: components["schemas"]["ImageOutput"];
sub: components["schemas"]["IntegerOutput"];
midas_depth_image_processor: components["schemas"]["ImageOutput"];
rectangle_mask: components["schemas"]["MaskOutput"];
img_watermark: components["schemas"]["ImageOutput"];
img_ilerp: components["schemas"]["ImageOutput"];
pidi_image_processor: components["schemas"]["ImageOutput"];
vae_loader: components["schemas"]["VAEOutput"];
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
float_range: components["schemas"]["FloatCollectionOutput"];
spandrel_image_to_image_autoscale: components["schemas"]["ImageOutput"]; spandrel_image_to_image_autoscale: components["schemas"]["ImageOutput"];
ip_adapter: components["schemas"]["IPAdapterOutput"]; create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
tile_to_properties: components["schemas"]["TileToPropertiesOutput"]; denoise_latents: components["schemas"]["LatentsOutput"];
integer_math: components["schemas"]["IntegerOutput"]; image_collection: components["schemas"]["ImageCollectionOutput"];
range: components["schemas"]["IntegerCollectionOutput"]; t2i_adapter: components["schemas"]["T2IAdapterOutput"];
normalbae_image_processor: components["schemas"]["ImageOutput"];
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
mul: components["schemas"]["IntegerOutput"];
image_mask_to_tensor: components["schemas"]["MaskOutput"];
face_mask_detection: components["schemas"]["FaceMaskOutput"];
save_image: components["schemas"]["ImageOutput"];
blank_image: components["schemas"]["ImageOutput"];
conditioning: components["schemas"]["ConditioningOutput"];
img_chan: components["schemas"]["ImageOutput"];
string_split: components["schemas"]["String2Output"];
segment_anything_processor: components["schemas"]["ImageOutput"];
unsharp_mask: components["schemas"]["ImageOutput"];
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
color: components["schemas"]["ColorOutput"];
range_of_size: components["schemas"]["IntegerCollectionOutput"];
face_identifier: components["schemas"]["ImageOutput"];
div: components["schemas"]["IntegerOutput"];
invert_tensor_mask: components["schemas"]["MaskOutput"];
step_param_easing: components["schemas"]["FloatCollectionOutput"];
merge_tiles_to_image: components["schemas"]["ImageOutput"];
latents: components["schemas"]["LatentsOutput"];
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
mediapipe_face_processor: components["schemas"]["ImageOutput"];
infill_lama: components["schemas"]["ImageOutput"];
compel: components["schemas"]["ConditioningOutput"];
round_float: components["schemas"]["FloatOutput"];
string_join: components["schemas"]["StringOutput"]; string_join: components["schemas"]["StringOutput"];
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
metadata: components["schemas"]["MetadataOutput"];
lineart_image_processor: components["schemas"]["ImageOutput"];
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
controlnet: components["schemas"]["ControlOutput"];
sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"];
img_lerp: components["schemas"]["ImageOutput"];
scheduler: components["schemas"]["SchedulerOutput"];
l2i: components["schemas"]["ImageOutput"];
img_paste: components["schemas"]["ImageOutput"];
tile_to_properties: components["schemas"]["TileToPropertiesOutput"];
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
depth_anything_image_processor: components["schemas"]["ImageOutput"];
prompt_from_file: components["schemas"]["StringCollectionOutput"];
integer: components["schemas"]["IntegerOutput"];
img_mul: components["schemas"]["ImageOutput"];
rand_int: components["schemas"]["IntegerOutput"];
string_join_three: components["schemas"]["StringOutput"];
img_blur: components["schemas"]["ImageOutput"]; img_blur: components["schemas"]["ImageOutput"];
float: components["schemas"]["FloatOutput"];
sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"];
crop_latents: components["schemas"]["LatentsOutput"];
model_identifier: components["schemas"]["ModelIdentifierOutput"];
show_image: components["schemas"]["ImageOutput"];
img_channel_offset: components["schemas"]["ImageOutput"];
i2l: components["schemas"]["LatentsOutput"];
core_metadata: components["schemas"]["MetadataOutput"];
infill_cv2: components["schemas"]["ImageOutput"];
lora_selector: components["schemas"]["LoRASelectorOutput"];
rand_float: components["schemas"]["FloatOutput"];
float_math: components["schemas"]["FloatOutput"];
main_model_loader: components["schemas"]["ModelLoaderOutput"];
image: components["schemas"]["ImageOutput"];
infill_patchmatch: components["schemas"]["ImageOutput"];
noise: components["schemas"]["NoiseOutput"];
tile_image_processor: components["schemas"]["ImageOutput"];
spandrel_image_to_image: components["schemas"]["ImageOutput"];
hed_image_processor: components["schemas"]["ImageOutput"];
heuristic_resize: components["schemas"]["ImageOutput"];
img_hue_adjust: components["schemas"]["ImageOutput"];
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
canvas_paste_back: components["schemas"]["ImageOutput"];
ip_adapter: components["schemas"]["IPAdapterOutput"];
img_nsfw: components["schemas"]["ImageOutput"];
range: components["schemas"]["IntegerCollectionOutput"];
string: components["schemas"]["StringOutput"];
seamless: components["schemas"]["SeamlessModeOutput"];
integer_collection: components["schemas"]["IntegerCollectionOutput"];
iterate: components["schemas"]["IterateInvocationOutput"];
img_scale: components["schemas"]["ImageOutput"];
pair_tile_image: components["schemas"]["PairTileImageOutput"];
canny_image_processor: components["schemas"]["ImageOutput"];
integer_math: components["schemas"]["IntegerOutput"];
leres_image_processor: components["schemas"]["ImageOutput"];
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
float_collection: components["schemas"]["FloatCollectionOutput"];
string_collection: components["schemas"]["StringCollectionOutput"];
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
mask_combine: components["schemas"]["ImageOutput"];
latents_collection: components["schemas"]["LatentsCollectionOutput"];
mask_edge: components["schemas"]["ImageOutput"];
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
img_conv: components["schemas"]["ImageOutput"];
color_correct: components["schemas"]["ImageOutput"];
lblend: components["schemas"]["LatentsOutput"];
mask_from_id: components["schemas"]["ImageOutput"];
img_pad_crop: components["schemas"]["ImageOutput"];
metadata_item: components["schemas"]["MetadataItemOutput"];
lora_loader: components["schemas"]["LoRALoaderOutput"];
cv_inpaint: components["schemas"]["ImageOutput"];
color_map_image_processor: components["schemas"]["ImageOutput"];
img_resize: components["schemas"]["ImageOutput"];
random_range: components["schemas"]["IntegerCollectionOutput"];
img_crop: components["schemas"]["ImageOutput"];
freeu: components["schemas"]["UNetOutput"];
infill_tile: components["schemas"]["ImageOutput"];
infill_rgba: components["schemas"]["ImageOutput"];
tomask: components["schemas"]["ImageOutput"];
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
boolean: components["schemas"]["BooleanOutput"];
}; };
/** /**
* InvocationStartedEvent * InvocationStartedEvent
@ -10142,34 +10142,6 @@ export type components = {
/** Ui Order */ /** Ui Order */
ui_order: number | null; ui_order: number | null;
}; };
/** PaginatedResults[StylePresetRecordDTO] */
PaginatedResults_StylePresetRecordDTO_: {
/**
* Page
* @description Current Page
*/
page: number;
/**
* Pages
* @description Total number of pages
*/
pages: number;
/**
* Per Page
* @description Number of items per page
*/
per_page: number;
/**
* Total
* @description Total number of items in result
*/
total: number;
/**
* Items
* @description Items
*/
items: components["schemas"]["StylePresetRecordDTO"][];
};
/** PaginatedResults[WorkflowRecordListItemDTO] */ /** PaginatedResults[WorkflowRecordListItemDTO] */
PaginatedResults_WorkflowRecordListItemDTO_: { PaginatedResults_WorkflowRecordListItemDTO_: {
/** /**
@ -12700,12 +12672,6 @@ export type components = {
*/ */
is_default: boolean; is_default: boolean;
}; };
/**
* StylePresetRecordOrderBy
* @description The order by options for workflow records
* @enum {string}
*/
StylePresetRecordOrderBy: "created_at" | "name";
/** StylePresetWithoutId */ /** StylePresetWithoutId */
StylePresetWithoutId: { StylePresetWithoutId: {
/** /**
@ -16331,31 +16297,11 @@ export type operations = {
* @description Gets a page of style presets * @description Gets a page of style presets
*/ */
list_style_presets: { list_style_presets: {
parameters: {
query?: {
/** @description The page to get */
page?: number;
/** @description The number of style presets per page */
per_page?: number;
/** @description The attribute to order by */
order_by?: components["schemas"]["StylePresetRecordOrderBy"];
/** @description The direction to order by */
direction?: components["schemas"]["SQLiteDirection"];
/** @description The text to query by (matches name and description) */
query?: string | null;
};
};
responses: { responses: {
/** @description Successful Response */ /** @description Successful Response */
200: { 200: {
content: { content: {
"application/json": components["schemas"]["PaginatedResults_StylePresetRecordDTO_"]; "application/json": components["schemas"]["StylePresetRecordDTO"][];
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
}; };
}; };
}; };