mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat: queued generation (#4502)
* fix(config): fix typing issues in `config/` `config/invokeai_config.py`: - use `Optional` for things that are optional - fix typing of `ram_cache_size()` and `vram_cache_size()` - remove unused and incorrectly typed method `autoconvert_path` - fix types and logic for `parse_args()`, in which `InvokeAIAppConfig.initconf` *must* be a `DictConfig`, but function would allow it to be set as a `ListConfig`, which presumably would cause issues elsewhere `config/base.py`: - use `cls` for first arg of class methods - use `Optional` for things that are optional - fix minor type issue related to setting of `env_prefix` - remove unused `add_subparser()` method, which calls `add_parser()` on an `ArgumentParser` (method only available on the `_SubParsersAction` object, which is returned from ArgumentParser.add_subparsers()`) * feat: queued generation and batches Due to a very messy branch with broad addition of `isort` on `main` alongside it, some git surgery was needed to get an agreeable git history. This commit represents all of the work on queued generation. See PR for notes. * chore: flake8, isort, black * fix(nodes): fix incorrect service stop() method * fix(nodes): improve names of a few variables * fix(tests): fix up tests after changes to batches/queue * feat(tests): add unit tests for session queue helper functions * feat(ui): dynamic prompts is always enabled * feat(queue): add queue_status_changed event * feat(ui): wip queue graphs * feat(nodes): move cleanup til after invoker startup * feat(nodes): add cancel_by_batch_ids * feat(ui): wip batch graphs & UI * fix(nodes): remove `Batch.batch_id` from required * fix(ui): cleanup and use fixedCacheKey for all mutations * fix(ui): remove orphaned nodes from canvas graphs * fix(nodes): fix cancel_by_batch_ids result count * fix(ui): only show cancel batch tooltip when batches were canceled * chore: isort * fix(api): return `[""]` when dynamic prompts generates no prompts Just a simple fallback so we always have a prompt. * feat(ui): dynamicPrompts.combinatorial is always on There seems to be little purpose in using the combinatorial generation for dynamic prompts. I've disabled it by hiding it from the UI and defaulting combinatorial to true. If we want to enable it again in the future it's straightforward to do so. * feat: add queue_id & support logic * feat(ui): fix upscale button It prepends the upscale operation to queue * feat(nodes): return queue item when enqueuing a single graph This facilitates one-off graph async workflows in the client. * feat(ui): move controlnet autoprocess to queue * fix(ui): fix non-serializable DOMRect in redux state * feat(ui): QueueTable performance tweaks * feat(ui): update queue list Queue items expand to show the full queue item. Just as JSON for now. * wip threaded session_processor * feat(nodes,ui): fully migrate queue to session_processor * feat(nodes,ui): add processor events * feat(ui): ui tweaks * feat(nodes,ui): consolidate events, reduce network requests * feat(ui): cleanup & abstract queue hooks * feat(nodes): optimize batch permutation Use a generator to do only as much work as is needed. Previously, though we only ended up creating exactly as many queue items as was needed, there was still some intermediary work that calculated *all* permutations. When that number was very high, the system had a very hard time and used a lot of memory. The logic has been refactored to use a generator. Additionally, the batch validators are optimized to return early and use less memory. * feat(ui): add seed behaviour parameter This dynamic prompts parameter allows the seed to be randomized per prompt or per iteration: - Per iteration: Use the same seed for all prompts in a single dynamic prompt expansion - Per prompt: Use a different seed for every single prompt "Per iteration" is appropriate for exploring a the latents space with a stable starting noise, while "Per prompt" provides more variation. * fix(ui): remove extraneous random seed nodes from linear graphs * fix(ui): fix controlnet autoprocess not working when queue is running * feat(queue): add timestamps to queue status updates Also show execution time in queue list * feat(queue): change all execution-related events to use the `queue_id` as the room, also include `queue_item_id` in InvocationQueueItem This allows for much simpler handling of queue items. * feat(api): deprecate sessions router * chore(backend): tidy logging in `dependencies.py` * fix(backend): respect `use_memory_db` * feat(backend): add `config.log_sql` (enables sql trace logging) * feat: add invocation cache Supersedes #4574 The invocation cache provides simple node memoization functionality. Nodes that use the cache are memoized and not re-executed if their inputs haven't changed. Instead, the stored output is returned. ## Results This feature provides anywhere some significant to massive performance improvement. The improvement is most marked on large batches of generations where you only change a couple things (e.g. different seed or prompt for each iteration) and low-VRAM systems, where skipping an extraneous model load is a big deal. ## Overview A new `invocation_cache` service is added to handle the caching. There's not much to it. All nodes now inherit a boolean `use_cache` field from `BaseInvocation`. This is a node field and not a class attribute, because specific instances of nodes may want to opt in or out of caching. The recently-added `invoke_internal()` method on `BaseInvocation` is used as an entrypoint for the cache logic. To create a cache key, the invocation is first serialized using pydantic's provided `json()` method, skipping the unique `id` field. Then python's very fast builtin `hash()` is used to create an integer key. All implementations of `InvocationCacheBase` must provide a class method `create_key()` which accepts an invocation and outputs a string or integer key. ## In-Memory Implementation An in-memory implementation is provided. In this implementation, the node outputs are stored in memory as python classes. The in-memory cache does not persist application restarts. Max node cache size is added as `node_cache_size` under the `Generation` config category. It defaults to 512 - this number is up for discussion, but given that these are relatively lightweight pydantic models, I think it's safe to up this even higher. Note that the cache isn't storing the big stuff - tensors and images are store on disk, and outputs include only references to them. ## Node Definition The default for all nodes is to use the cache. The `@invocation` decorator now accepts an optional `use_cache: bool` argument to override the default of `True`. Non-deterministic nodes, however, should set this to `False`. Currently, all random-stuff nodes, including `dynamic_prompt`, are set to `False`. The field name `use_cache` is now effectively a reserved field name and possibly a breaking change if any community nodes use this as a field name. In hindsight, all our reserved field names should have been prefixed with underscores or something. ## One Gotcha Leaf nodes probably want to opt out of the cache, because if they are not cached, their outputs are not saved again. If you run the same graph multiple times, you only end up with a single image output, because the image storage side-effects are in the `invoke()` method, which is bypassed if we have a cache hit. ## Linear UI The linear graphs _almost_ just work, but due to the gotcha, we need to be careful about the final image-outputting node. To resolve this, a `SaveImageInvocation` node is added and used in the linear graphs. This node is similar to `ImagePrimitive`, except it saves a copy of its input image, and has `use_cache` set to `False` by default. This is now the leaf node in all linear graphs, and is the only node in those graphs with `use_cache == False` _and_ the only node with `is_intermedate == False`. ## Workflow Editor All nodes now have a footer with a new `Use Cache [ ]` checkbox. It defaults to the value set by the invocation in its python definition, but can be changed by the user. The workflow/node validation logic has been updated to migrate old workflows to use the new default values for `use_cache`. Users may still want to review the settings that have been chosen. In the event of catastrophic failure when running this migration, the default value of `True` is applied, as this is correct for most nodes. Users should consider saving their workflows after loading them in and having them updated. ## Future Enhancements - Callback A future enhancement would be to provide a callback to the `use_cache` flag that would be run as the node is executed to determine, based on its own internal state, if the cache should be used or not. This would be useful for `DynamicPromptInvocation`, where the deterministic behaviour is determined by the `combinatorial: bool` field. ## Future Enhancements - Persisted Cache Similar to how the latents storage is backed by disk, the invocation cache could be persisted to the database or disk. We'd need to be very careful about deserializing outputs, but it's perhaps worth exploring in the future. * fix(ui): fix queue list item width * feat(nodes): do not send the whole node on every generator progress * feat(ui): strip out old logic related to sessions Things like `isProcessing` are no longer relevant with queue. Removed them all & updated everything be appropriate for queue. May be a few little quirks I've missed... * feat(ui): fix up param collapse labels * feat(ui): click queue count to go to queue tab * tidy(queue): update comment, query format * feat(ui): fix progress bar when canceling * fix(ui): fix circular dependency * feat(nodes): bail on node caching logic if `node_cache_size == 0` * feat(nodes): handle KeyError on node cache pop * feat(nodes): bypass cache codepath if caches is disabled more better no do thing * fix(ui): reset api cache on connect/disconnect * feat(ui): prevent enqueue when no prompts generated * feat(ui): add queue controls to workflow editor * feat(ui): update floating buttons & other incidental UI tweaks * fix(ui): fix missing/incorrect translation keys * fix(tests): add config service to mock invocation services invoking needs access to `node_cache_size` to occur * optionally remove pause/resume buttons from queue UI * option to disable prepending * chore(ui): remove unused file * feat(queue): remove `order_id` entirely, `item_id` is now an autoinc pk --------- Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local>
This commit is contained in:
@ -153,8 +153,8 @@ const IAICanvas = () => {
|
||||
});
|
||||
|
||||
resizeObserver.observe(containerRef.current);
|
||||
|
||||
dispatch(canvasResized(containerRef.current.getBoundingClientRect()));
|
||||
const { width, height } = containerRef.current.getBoundingClientRect();
|
||||
dispatch(canvasResized({ width, height }));
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
|
@ -1,23 +1,24 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import { ImageConfig } from 'konva/lib/shapes/Image';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { Image as KonvaImage } from 'react-konva';
|
||||
import { canvasSelector } from '../store/canvasSelectors';
|
||||
|
||||
const selector = createSelector(
|
||||
[systemSelector, canvasSelector],
|
||||
(system, canvas) => {
|
||||
const { progressImage, sessionId } = system;
|
||||
const { sessionId: canvasSessionId, boundingBox } =
|
||||
canvas.layerState.stagingArea;
|
||||
[stateSelector],
|
||||
({ system, canvas }) => {
|
||||
const { denoiseProgress } = system;
|
||||
const { boundingBox } = canvas.layerState.stagingArea;
|
||||
const { sessionIds } = canvas;
|
||||
|
||||
return {
|
||||
boundingBox,
|
||||
progressImage: sessionId === canvasSessionId ? progressImage : undefined,
|
||||
progressImage:
|
||||
denoiseProgress && sessionIds.includes(denoiseProgress.session_id)
|
||||
? denoiseProgress.progress_image
|
||||
: undefined,
|
||||
};
|
||||
},
|
||||
{
|
||||
|
@ -11,8 +11,9 @@ import {
|
||||
setShouldShowStagingImage,
|
||||
setShouldShowStagingOutline,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -25,16 +26,15 @@ import {
|
||||
FaPlus,
|
||||
FaSave,
|
||||
} from 'react-icons/fa';
|
||||
import { stagingAreaImageSaved } from '../store/actions';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
||||
import { stagingAreaImageSaved } from '../store/actions';
|
||||
|
||||
const selector = createSelector(
|
||||
[canvasSelector],
|
||||
(canvas) => {
|
||||
const {
|
||||
layerState: {
|
||||
stagingArea: { images, selectedImageIndex, sessionId },
|
||||
stagingArea: { images, selectedImageIndex },
|
||||
},
|
||||
shouldShowStagingOutline,
|
||||
shouldShowStagingImage,
|
||||
@ -47,14 +47,9 @@ const selector = createSelector(
|
||||
isOnLastImage: selectedImageIndex === images.length - 1,
|
||||
shouldShowStagingImage,
|
||||
shouldShowStagingOutline,
|
||||
sessionId,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: isEqual,
|
||||
},
|
||||
}
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const IAICanvasStagingAreaToolbar = () => {
|
||||
@ -64,7 +59,6 @@ const IAICanvasStagingAreaToolbar = () => {
|
||||
isOnLastImage,
|
||||
currentStagingAreaImage,
|
||||
shouldShowStagingImage,
|
||||
sessionId,
|
||||
} = useAppSelector(selector);
|
||||
|
||||
const { t } = useTranslation();
|
||||
@ -121,8 +115,8 @@ const IAICanvasStagingAreaToolbar = () => {
|
||||
);
|
||||
|
||||
const handleAccept = useCallback(
|
||||
() => dispatch(commitStagingAreaImage(sessionId)),
|
||||
[dispatch, sessionId]
|
||||
() => dispatch(commitStagingAreaImage()),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const { data: imageDTO } = useGetImageDTOQuery(
|
||||
|
@ -1,24 +1,23 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { FaRedo } from 'react-icons/fa';
|
||||
|
||||
import { redo } from 'features/canvas/store/canvasSlice';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const canvasRedoSelector = createSelector(
|
||||
[canvasSelector, activeTabNameSelector, systemSelector],
|
||||
(canvas, activeTabName, system) => {
|
||||
[stateSelector, activeTabNameSelector],
|
||||
({ canvas }, activeTabName) => {
|
||||
const { futureLayerStates } = canvas;
|
||||
|
||||
return {
|
||||
canRedo: futureLayerStates.length > 0 && !system.isProcessing,
|
||||
canRedo: futureLayerStates.length > 0,
|
||||
activeTabName,
|
||||
};
|
||||
},
|
||||
|
@ -1,14 +1,12 @@
|
||||
import { ButtonGroup, Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import {
|
||||
canvasSelector,
|
||||
isStagingSelector,
|
||||
} from 'features/canvas/store/canvasSelectors';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
addEraseRect,
|
||||
addFillRect,
|
||||
@ -16,7 +14,6 @@ import {
|
||||
setBrushSize,
|
||||
setTool,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import { clamp, isEqual } from 'lodash-es';
|
||||
import { memo } from 'react';
|
||||
|
||||
@ -32,15 +29,13 @@ import {
|
||||
} from 'react-icons/fa';
|
||||
|
||||
export const selector = createSelector(
|
||||
[canvasSelector, isStagingSelector, systemSelector],
|
||||
(canvas, isStaging, system) => {
|
||||
const { isProcessing } = system;
|
||||
[stateSelector, isStagingSelector],
|
||||
({ canvas }, isStaging) => {
|
||||
const { tool, brushColor, brushSize } = canvas;
|
||||
|
||||
return {
|
||||
tool,
|
||||
isStaging,
|
||||
isProcessing,
|
||||
brushColor,
|
||||
brushSize,
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Box, ButtonGroup, Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||
@ -11,10 +12,7 @@ import {
|
||||
canvasMerged,
|
||||
canvasSavedToGallery,
|
||||
} from 'features/canvas/store/actions';
|
||||
import {
|
||||
canvasSelector,
|
||||
isStagingSelector,
|
||||
} from 'features/canvas/store/canvasSelectors';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
resetCanvas,
|
||||
resetCanvasView,
|
||||
@ -27,9 +25,9 @@ import {
|
||||
LAYER_NAMES_DICT,
|
||||
} from 'features/canvas/store/canvasTypes';
|
||||
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { memo } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@ -47,17 +45,14 @@ import IAICanvasRedoButton from './IAICanvasRedoButton';
|
||||
import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover';
|
||||
import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions';
|
||||
import IAICanvasUndoButton from './IAICanvasUndoButton';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const selector = createSelector(
|
||||
[systemSelector, canvasSelector, isStagingSelector],
|
||||
(system, canvas, isStaging) => {
|
||||
const { isProcessing } = system;
|
||||
[stateSelector, isStagingSelector],
|
||||
({ canvas }, isStaging) => {
|
||||
const { tool, shouldCropToBoundingBoxOnSave, layer, isMaskEnabled } =
|
||||
canvas;
|
||||
|
||||
return {
|
||||
isProcessing,
|
||||
isStaging,
|
||||
isMaskEnabled,
|
||||
tool,
|
||||
@ -74,8 +69,7 @@ export const selector = createSelector(
|
||||
|
||||
const IAICanvasToolbar = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { isProcessing, isStaging, isMaskEnabled, layer, tool } =
|
||||
useAppSelector(selector);
|
||||
const { isStaging, isMaskEnabled, layer, tool } = useAppSelector(selector);
|
||||
const canvasBaseLayer = getCanvasBaseLayer();
|
||||
|
||||
const { t } = useTranslation();
|
||||
@ -118,7 +112,7 @@ const IAICanvasToolbar = () => {
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[canvasBaseLayer, isProcessing]
|
||||
[canvasBaseLayer]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
@ -130,7 +124,7 @@ const IAICanvasToolbar = () => {
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[canvasBaseLayer, isProcessing]
|
||||
[canvasBaseLayer]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
@ -142,7 +136,7 @@ const IAICanvasToolbar = () => {
|
||||
enabled: () => !isStaging && isClipboardAPIAvailable,
|
||||
preventDefault: true,
|
||||
},
|
||||
[canvasBaseLayer, isProcessing, isClipboardAPIAvailable]
|
||||
[canvasBaseLayer, isClipboardAPIAvailable]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
@ -154,7 +148,7 @@ const IAICanvasToolbar = () => {
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[canvasBaseLayer, isProcessing]
|
||||
[canvasBaseLayer]
|
||||
);
|
||||
|
||||
const handleSelectMoveTool = () => dispatch(setTool('move'));
|
||||
|
@ -1,24 +1,23 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { FaUndo } from 'react-icons/fa';
|
||||
|
||||
import { undo } from 'features/canvas/store/canvasSlice';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
|
||||
const canvasUndoSelector = createSelector(
|
||||
[canvasSelector, activeTabNameSelector, systemSelector],
|
||||
(canvas, activeTabName, system) => {
|
||||
[stateSelector, activeTabNameSelector],
|
||||
({ canvas }, activeTabName) => {
|
||||
const { pastLayerStates } = canvas;
|
||||
|
||||
return {
|
||||
canUndo: pastLayerStates.length > 0 && !system.isProcessing,
|
||||
canUndo: pastLayerStates.length > 0,
|
||||
activeTabName,
|
||||
};
|
||||
},
|
||||
|
@ -1,16 +1,12 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { RootState, stateSelector } from 'app/store/store';
|
||||
import { CanvasImage, CanvasState, isCanvasBaseImage } from './canvasTypes';
|
||||
|
||||
export const canvasSelector = (state: RootState): CanvasState => state.canvas;
|
||||
|
||||
export const isStagingSelector = createSelector(
|
||||
[canvasSelector, activeTabNameSelector, systemSelector],
|
||||
(canvas, activeTabName, system) =>
|
||||
canvas.layerState.stagingArea.images.length > 0 ||
|
||||
(activeTabName === 'unifiedCanvas' && system.isProcessing)
|
||||
[stateSelector],
|
||||
({ canvas }) => canvas.layerState.stagingArea.images.length > 0
|
||||
);
|
||||
|
||||
export const initialCanvasImageSelector = (
|
||||
|
@ -85,6 +85,8 @@ export const initialCanvasState: CanvasState = {
|
||||
stageDimensions: { width: 0, height: 0 },
|
||||
stageScale: 1,
|
||||
tool: 'brush',
|
||||
sessionIds: [],
|
||||
batchIds: [],
|
||||
};
|
||||
|
||||
export const canvasSlice = createSlice({
|
||||
@ -297,18 +299,26 @@ export const canvasSlice = createSlice({
|
||||
setIsMoveStageKeyHeld: (state, action: PayloadAction<boolean>) => {
|
||||
state.isMoveStageKeyHeld = action.payload;
|
||||
},
|
||||
canvasSessionIdChanged: (state, action: PayloadAction<string>) => {
|
||||
state.layerState.stagingArea.sessionId = action.payload;
|
||||
canvasBatchIdAdded: (state, action: PayloadAction<string>) => {
|
||||
state.batchIds.push(action.payload);
|
||||
},
|
||||
canvasSessionIdAdded: (state, action: PayloadAction<string>) => {
|
||||
state.sessionIds.push(action.payload);
|
||||
},
|
||||
canvasBatchesAndSessionsReset: (state) => {
|
||||
state.sessionIds = [];
|
||||
state.batchIds = [];
|
||||
},
|
||||
stagingAreaInitialized: (
|
||||
state,
|
||||
action: PayloadAction<{ sessionId: string; boundingBox: IRect }>
|
||||
action: PayloadAction<{
|
||||
boundingBox: IRect;
|
||||
}>
|
||||
) => {
|
||||
const { sessionId, boundingBox } = action.payload;
|
||||
const { boundingBox } = action.payload;
|
||||
|
||||
state.layerState.stagingArea = {
|
||||
boundingBox,
|
||||
sessionId,
|
||||
images: [],
|
||||
selectedImageIndex: -1,
|
||||
};
|
||||
@ -632,10 +642,7 @@ export const canvasSlice = createSlice({
|
||||
0
|
||||
);
|
||||
},
|
||||
commitStagingAreaImage: (
|
||||
state,
|
||||
_action: PayloadAction<string | undefined>
|
||||
) => {
|
||||
commitStagingAreaImage: (state) => {
|
||||
if (!state.layerState.stagingArea.images.length) {
|
||||
return;
|
||||
}
|
||||
@ -869,9 +876,11 @@ export const {
|
||||
setScaledBoundingBoxDimensions,
|
||||
setShouldRestrictStrokesToBox,
|
||||
stagingAreaInitialized,
|
||||
canvasSessionIdChanged,
|
||||
setShouldAntialias,
|
||||
canvasResized,
|
||||
canvasBatchIdAdded,
|
||||
canvasSessionIdAdded,
|
||||
canvasBatchesAndSessionsReset,
|
||||
} = canvasSlice.actions;
|
||||
|
||||
export default canvasSlice.reducer;
|
||||
|
@ -89,7 +89,6 @@ export type CanvasLayerState = {
|
||||
stagingArea: {
|
||||
images: CanvasImage[];
|
||||
selectedImageIndex: number;
|
||||
sessionId?: string;
|
||||
boundingBox?: IRect;
|
||||
};
|
||||
};
|
||||
@ -166,6 +165,8 @@ export interface CanvasState {
|
||||
stageScale: number;
|
||||
tool: CanvasTool;
|
||||
generationMode?: GenerationMode;
|
||||
batchIds: string[];
|
||||
sessionIds: string[];
|
||||
}
|
||||
|
||||
export type GenerationMode = 'txt2img' | 'img2img' | 'inpaint' | 'outpaint';
|
||||
|
@ -3,7 +3,7 @@ import { memo, useCallback } from 'react';
|
||||
import { ControlNetConfig } from '../store/controlNetSlice';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { controlNetImageProcessed } from '../store/actions';
|
||||
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
|
||||
import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue';
|
||||
|
||||
type Props = {
|
||||
controlNet: ControlNetConfig;
|
||||
@ -12,7 +12,7 @@ type Props = {
|
||||
const ControlNetPreprocessButton = (props: Props) => {
|
||||
const { controlNetId, controlImage } = props.controlNet;
|
||||
const dispatch = useAppDispatch();
|
||||
const isReady = useIsReadyToInvoke();
|
||||
const isReady = useIsReadyToEnqueue();
|
||||
|
||||
const handleProcess = useCallback(() => {
|
||||
dispatch(
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import {
|
||||
ControlNetConfig,
|
||||
controlNetAutoConfigToggled,
|
||||
} from 'features/controlNet/store/controlNetSlice';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -15,7 +14,6 @@ type Props = {
|
||||
const ParamControlNetShouldAutoConfig = (props: Props) => {
|
||||
const { controlNetId, isEnabled, shouldAutoConfig } = props.controlNet;
|
||||
const dispatch = useAppDispatch();
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleShouldAutoConfigChanged = useCallback(() => {
|
||||
@ -28,7 +26,7 @@ const ParamControlNetShouldAutoConfig = (props: Props) => {
|
||||
aria-label={t('controlnet.autoConfigure')}
|
||||
isChecked={shouldAutoConfig}
|
||||
onChange={handleShouldAutoConfigChanged}
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -11,11 +11,10 @@ import {
|
||||
} from 'features/controlNet/store/controlNetSlice';
|
||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||
import { modelIdToControlNetModelParam } from 'features/parameters/util/modelIdToControlNetModelParam';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { forEach } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useGetControlNetModelsQuery } from 'services/api/endpoints/models';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetControlNetModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
type ParamControlNetModelProps = {
|
||||
controlNet: ControlNetConfig;
|
||||
@ -33,7 +32,6 @@ const selector = createSelector(
|
||||
const ParamControlNetModel = (props: ParamControlNetModelProps) => {
|
||||
const { controlNetId, model: controlNetModel, isEnabled } = props.controlNet;
|
||||
const dispatch = useAppDispatch();
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
|
||||
const { mainModel } = useAppSelector(selector);
|
||||
const { t } = useTranslation();
|
||||
@ -110,7 +108,7 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => {
|
||||
placeholder={t('controlnet.selectModel')}
|
||||
value={selectedModel?.id ?? null}
|
||||
onChange={handleModelChanged}
|
||||
disabled={isBusy || !isEnabled}
|
||||
disabled={!isEnabled}
|
||||
tooltip={selectedModel?.description}
|
||||
/>
|
||||
);
|
||||
|
@ -6,7 +6,6 @@ import IAIMantineSearchableSelect, {
|
||||
IAISelectDataType,
|
||||
} from 'common/components/IAIMantineSearchableSelect';
|
||||
import { configSelector } from 'features/system/store/configSelectors';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { map } from 'lodash-es';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { CONTROLNET_PROCESSORS } from '../../store/constants';
|
||||
@ -56,7 +55,6 @@ const ParamControlNetProcessorSelect = (
|
||||
) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { controlNetId, isEnabled, processorNode } = props.controlNet;
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const controlNetProcessors = useAppSelector(selector);
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -78,7 +76,7 @@ const ParamControlNetProcessorSelect = (
|
||||
value={processorNode.type ?? 'canny_image_processor'}
|
||||
data={controlNetProcessors}
|
||||
onChange={handleProcessorTypeChanged}
|
||||
disabled={isBusy || !isEnabled}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredCannyImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.canny_image_processor
|
||||
.default as RequiredCannyImageProcessorInvocation;
|
||||
@ -20,7 +18,6 @@ type CannyProcessorProps = {
|
||||
const CannyProcessor = (props: CannyProcessorProps) => {
|
||||
const { controlNetId, processorNode, isEnabled } = props;
|
||||
const { low_threshold, high_threshold } = processorNode;
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -53,7 +50,7 @@ const CannyProcessor = (props: CannyProcessorProps) => {
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<IAISlider
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
label={t('controlnet.lowThreshold')}
|
||||
value={low_threshold}
|
||||
onChange={handleLowThresholdChanged}
|
||||
@ -65,7 +62,7 @@ const CannyProcessor = (props: CannyProcessorProps) => {
|
||||
withSliderMarks
|
||||
/>
|
||||
<IAISlider
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
label={t('controlnet.highThreshold')}
|
||||
value={high_threshold}
|
||||
onChange={handleHighThresholdChanged}
|
||||
|
@ -2,11 +2,9 @@ import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredContentShuffleImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.content_shuffle_image_processor
|
||||
.default as RequiredContentShuffleImageProcessorInvocation;
|
||||
@ -21,7 +19,6 @@ const ContentShuffleProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode, isEnabled } = props;
|
||||
const { image_resolution, detect_resolution, w, h, f } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
@ -101,7 +98,7 @@ const ContentShuffleProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.imageResolution')}
|
||||
@ -113,7 +110,7 @@ const ContentShuffleProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.w')}
|
||||
@ -125,7 +122,7 @@ const ContentShuffleProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.h')}
|
||||
@ -137,7 +134,7 @@ const ContentShuffleProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.f')}
|
||||
@ -149,7 +146,7 @@ const ContentShuffleProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredHedImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.hed_image_processor
|
||||
.default as RequiredHedImageProcessorInvocation;
|
||||
@ -24,7 +22,6 @@ const HedPreprocessor = (props: HedProcessorProps) => {
|
||||
processorNode: { detect_resolution, image_resolution, scribble },
|
||||
isEnabled,
|
||||
} = props;
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -73,7 +70,7 @@ const HedPreprocessor = (props: HedProcessorProps) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.imageResolution')}
|
||||
@ -85,13 +82,13 @@ const HedPreprocessor = (props: HedProcessorProps) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISwitch
|
||||
label={t('controlnet.scribble')}
|
||||
isChecked={scribble}
|
||||
onChange={handleScribbleChanged}
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredLineartAnimeImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.lineart_anime_image_processor
|
||||
.default as RequiredLineartAnimeImageProcessorInvocation;
|
||||
@ -21,7 +19,6 @@ const LineartAnimeProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode, isEnabled } = props;
|
||||
const { image_resolution, detect_resolution } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
@ -62,7 +59,7 @@ const LineartAnimeProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.imageResolution')}
|
||||
@ -74,7 +71,7 @@ const LineartAnimeProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredLineartImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.lineart_image_processor
|
||||
.default as RequiredLineartImageProcessorInvocation;
|
||||
@ -22,7 +20,6 @@ const LineartProcessor = (props: LineartProcessorProps) => {
|
||||
const { controlNetId, processorNode, isEnabled } = props;
|
||||
const { image_resolution, detect_resolution, coarse } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
@ -70,7 +67,7 @@ const LineartProcessor = (props: LineartProcessorProps) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.imageResolution')}
|
||||
@ -82,13 +79,13 @@ const LineartProcessor = (props: LineartProcessorProps) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISwitch
|
||||
label={t('controlnet.coarse')}
|
||||
isChecked={coarse}
|
||||
onChange={handleCoarseChanged}
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredMediapipeFaceProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.mediapipe_face_processor
|
||||
.default as RequiredMediapipeFaceProcessorInvocation;
|
||||
@ -21,7 +19,6 @@ const MediapipeFaceProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode, isEnabled } = props;
|
||||
const { max_faces, min_confidence } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleMaxFacesChanged = useCallback(
|
||||
@ -58,7 +55,7 @@ const MediapipeFaceProcessor = (props: Props) => {
|
||||
max={20}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.minConfidence')}
|
||||
@ -71,7 +68,7 @@ const MediapipeFaceProcessor = (props: Props) => {
|
||||
step={0.01}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredMidasDepthImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.midas_depth_image_processor
|
||||
.default as RequiredMidasDepthImageProcessorInvocation;
|
||||
@ -21,7 +19,6 @@ const MidasDepthProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode, isEnabled } = props;
|
||||
const { a_mult, bg_th } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleAMultChanged = useCallback(
|
||||
@ -59,7 +56,7 @@ const MidasDepthProcessor = (props: Props) => {
|
||||
step={0.01}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.bgth')}
|
||||
@ -72,7 +69,7 @@ const MidasDepthProcessor = (props: Props) => {
|
||||
step={0.01}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredMlsdImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.mlsd_image_processor
|
||||
.default as RequiredMlsdImageProcessorInvocation;
|
||||
@ -21,7 +19,6 @@ const MlsdImageProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode, isEnabled } = props;
|
||||
const { image_resolution, detect_resolution, thr_d, thr_v } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
@ -84,7 +81,7 @@ const MlsdImageProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.imageResolution')}
|
||||
@ -96,7 +93,7 @@ const MlsdImageProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.w')}
|
||||
@ -109,7 +106,7 @@ const MlsdImageProcessor = (props: Props) => {
|
||||
step={0.01}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.h')}
|
||||
@ -122,7 +119,7 @@ const MlsdImageProcessor = (props: Props) => {
|
||||
step={0.01}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredNormalbaeImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.normalbae_image_processor
|
||||
.default as RequiredNormalbaeImageProcessorInvocation;
|
||||
@ -21,7 +19,6 @@ const NormalBaeProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode, isEnabled } = props;
|
||||
const { image_resolution, detect_resolution } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
@ -62,7 +59,7 @@ const NormalBaeProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.imageResolution')}
|
||||
@ -74,7 +71,7 @@ const NormalBaeProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredOpenposeImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.openpose_image_processor
|
||||
.default as RequiredOpenposeImageProcessorInvocation;
|
||||
@ -22,7 +20,6 @@ const OpenposeProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode, isEnabled } = props;
|
||||
const { image_resolution, detect_resolution, hand_and_face } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
@ -70,7 +67,7 @@ const OpenposeProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.imageResolution')}
|
||||
@ -82,13 +79,13 @@ const OpenposeProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISwitch
|
||||
label={t('controlnet.handAndFace')}
|
||||
isChecked={hand_and_face}
|
||||
onChange={handleHandAndFaceChanged}
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { CONTROLNET_PROCESSORS } from 'features/controlNet/store/constants';
|
||||
import { RequiredPidiImageProcessorInvocation } from 'features/controlNet/store/types';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
|
||||
import ProcessorWrapper from './common/ProcessorWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DEFAULTS = CONTROLNET_PROCESSORS.pidi_image_processor
|
||||
.default as RequiredPidiImageProcessorInvocation;
|
||||
@ -22,7 +20,6 @@ const PidiProcessor = (props: Props) => {
|
||||
const { controlNetId, processorNode, isEnabled } = props;
|
||||
const { image_resolution, detect_resolution, scribble, safe } = processorNode;
|
||||
const processorChanged = useProcessorNodeChanged();
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleDetectResolutionChanged = useCallback(
|
||||
@ -77,7 +74,7 @@ const PidiProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISlider
|
||||
label={t('controlnet.imageResolution')}
|
||||
@ -89,7 +86,7 @@ const PidiProcessor = (props: Props) => {
|
||||
max={4096}
|
||||
withInput
|
||||
withSliderMarks
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
<IAISwitch
|
||||
label={t('controlnet.scribble')}
|
||||
@ -100,7 +97,7 @@ const PidiProcessor = (props: Props) => {
|
||||
label={t('controlnet.safe')}
|
||||
isChecked={safe}
|
||||
onChange={handleSafeChanged}
|
||||
isDisabled={isBusy || !isEnabled}
|
||||
isDisabled={!isEnabled}
|
||||
/>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
|
@ -1,20 +1,9 @@
|
||||
import { IconButtonProps } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaTrash } from 'react-icons/fa';
|
||||
|
||||
const deleteImageButtonsSelector = createSelector(
|
||||
[stateSelector],
|
||||
({ system }) => {
|
||||
const { isProcessing, isConnected } = system;
|
||||
|
||||
return isConnected && !isProcessing;
|
||||
}
|
||||
);
|
||||
|
||||
type DeleteImageButtonProps = Omit<IconButtonProps, 'aria-label'> & {
|
||||
onClick: () => void;
|
||||
};
|
||||
@ -22,7 +11,7 @@ type DeleteImageButtonProps = Omit<IconButtonProps, 'aria-label'> & {
|
||||
export const DeleteImageButton = (props: DeleteImageButtonProps) => {
|
||||
const { onClick, isDisabled } = props;
|
||||
const { t } = useTranslation();
|
||||
const canDeleteImage = useAppSelector(deleteImageButtonsSelector);
|
||||
const isConnected = useAppSelector((state) => state.system.isConnected);
|
||||
|
||||
return (
|
||||
<IAIIconButton
|
||||
@ -30,7 +19,7 @@ export const DeleteImageButton = (props: DeleteImageButtonProps) => {
|
||||
icon={<FaTrash />}
|
||||
tooltip={`${t('gallery.deleteImage')} (Del)`}
|
||||
aria-label={`${t('gallery.deleteImage')} (Del)`}
|
||||
isDisabled={isDisabled || !canDeleteImage}
|
||||
isDisabled={isDisabled || !isConnected}
|
||||
colorScheme="error"
|
||||
/>
|
||||
);
|
||||
|
@ -2,28 +2,33 @@ import { Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import IAICollapse from 'common/components/IAICollapse';
|
||||
import { memo } from 'react';
|
||||
import { useFeatureStatus } from '../../system/hooks/useFeatureStatus';
|
||||
import ParamDynamicPromptsCombinatorial from './ParamDynamicPromptsCombinatorial';
|
||||
import ParamDynamicPromptsToggle from './ParamDynamicPromptsEnabled';
|
||||
import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
(state) => {
|
||||
const { isEnabled } = state.dynamicPrompts;
|
||||
|
||||
return { activeLabel: isEnabled ? 'Enabled' : undefined };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
import { useFeatureStatus } from '../../system/hooks/useFeatureStatus';
|
||||
import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts';
|
||||
import ParamDynamicPromptsPreview from './ParamDynamicPromptsPreview';
|
||||
import ParamDynamicPromptsSeedBehaviour from './ParamDynamicPromptsSeedBehaviour';
|
||||
|
||||
const ParamDynamicPromptsCollapse = () => {
|
||||
const { activeLabel } = useAppSelector(selector);
|
||||
const { t } = useTranslation();
|
||||
const selectActiveLabel = useMemo(
|
||||
() =>
|
||||
createSelector(stateSelector, ({ dynamicPrompts }) => {
|
||||
const count = dynamicPrompts.prompts.length;
|
||||
if (count === 1) {
|
||||
return t('dynamicPrompts.promptsWithCount_one', {
|
||||
count,
|
||||
});
|
||||
} else {
|
||||
return t('dynamicPrompts.promptsWithCount_other', {
|
||||
count,
|
||||
});
|
||||
}
|
||||
}),
|
||||
[t]
|
||||
);
|
||||
const activeLabel = useAppSelector(selectActiveLabel);
|
||||
|
||||
const isDynamicPromptingEnabled =
|
||||
useFeatureStatus('dynamicPrompting').isFeatureEnabled;
|
||||
@ -33,10 +38,13 @@ const ParamDynamicPromptsCollapse = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<IAICollapse label={t('prompt.dynamicPrompts')} activeLabel={activeLabel}>
|
||||
<IAICollapse
|
||||
label={t('dynamicPrompts.dynamicPrompts')}
|
||||
activeLabel={activeLabel}
|
||||
>
|
||||
<Flex sx={{ gap: 2, flexDir: 'column' }}>
|
||||
<ParamDynamicPromptsToggle />
|
||||
<ParamDynamicPromptsCombinatorial />
|
||||
<ParamDynamicPromptsSeedBehaviour />
|
||||
<ParamDynamicPromptsPreview />
|
||||
<ParamDynamicPromptsMaxPrompts />
|
||||
</Flex>
|
||||
</IAICollapse>
|
||||
|
@ -10,15 +10,15 @@ import { useTranslation } from 'react-i18next';
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
(state) => {
|
||||
const { combinatorial, isEnabled } = state.dynamicPrompts;
|
||||
const { combinatorial } = state.dynamicPrompts;
|
||||
|
||||
return { combinatorial, isDisabled: !isEnabled };
|
||||
return { combinatorial };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const ParamDynamicPromptsCombinatorial = () => {
|
||||
const { combinatorial, isDisabled } = useAppSelector(selector);
|
||||
const { combinatorial } = useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -28,8 +28,7 @@ const ParamDynamicPromptsCombinatorial = () => {
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
isDisabled={isDisabled}
|
||||
label={t('prompt.combinatorial')}
|
||||
label={t('dynamicPrompts.combinatorial')}
|
||||
isChecked={combinatorial}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
@ -1,38 +0,0 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { isEnabledToggled } from '../store/dynamicPromptsSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
(state) => {
|
||||
const { isEnabled } = state.dynamicPrompts;
|
||||
|
||||
return { isEnabled };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const ParamDynamicPromptsToggle = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { isEnabled } = useAppSelector(selector);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleToggleIsEnabled = useCallback(() => {
|
||||
dispatch(isEnabledToggled());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
label={t('prompt.enableDynamicPrompts')}
|
||||
isChecked={isEnabled}
|
||||
onChange={handleToggleIsEnabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamDynamicPromptsToggle);
|
@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
(state) => {
|
||||
const { maxPrompts, combinatorial, isEnabled } = state.dynamicPrompts;
|
||||
const { maxPrompts, combinatorial } = state.dynamicPrompts;
|
||||
const { min, sliderMax, inputMax } =
|
||||
state.config.sd.dynamicPrompts.maxPrompts;
|
||||
|
||||
@ -22,7 +22,7 @@ const selector = createSelector(
|
||||
min,
|
||||
sliderMax,
|
||||
inputMax,
|
||||
isDisabled: !isEnabled || !combinatorial,
|
||||
isDisabled: !combinatorial,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
@ -47,7 +47,7 @@ const ParamDynamicPromptsMaxPrompts = () => {
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
label={t('prompt.maxPrompts')}
|
||||
label={t('dynamicPrompts.maxPrompts')}
|
||||
isDisabled={isDisabled}
|
||||
min={min}
|
||||
max={sliderMax}
|
||||
|
@ -0,0 +1,100 @@
|
||||
import {
|
||||
ChakraProps,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
ListItem,
|
||||
OrderedList,
|
||||
Spinner,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
|
||||
import { memo } from 'react';
|
||||
import { FaCircleExclamation } from 'react-icons/fa6';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
(state) => {
|
||||
const { isLoading, isError, prompts, parsingError } = state.dynamicPrompts;
|
||||
|
||||
return {
|
||||
prompts,
|
||||
parsingError,
|
||||
isError,
|
||||
isLoading,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const listItemStyles: ChakraProps['sx'] = {
|
||||
'&::marker': { color: 'base.500', _dark: { color: 'base.500' } },
|
||||
};
|
||||
|
||||
const ParamDynamicPromptsPreview = () => {
|
||||
const { prompts, parsingError, isLoading, isError } =
|
||||
useAppSelector(selector);
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Flex
|
||||
w="full"
|
||||
h="full"
|
||||
layerStyle="second"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
p={8}
|
||||
>
|
||||
<IAINoContentFallback
|
||||
icon={FaCircleExclamation}
|
||||
label="Problem generating prompts"
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl isInvalid={Boolean(parsingError)}>
|
||||
<FormLabel whiteSpace="nowrap" overflow="hidden" textOverflow="ellipsis">
|
||||
Prompts Preview ({prompts.length}){parsingError && ` - ${parsingError}`}
|
||||
</FormLabel>
|
||||
<Flex h={64} pos="relative" layerStyle="third" borderRadius="base" p={2}>
|
||||
<ScrollableContent>
|
||||
<OrderedList stylePosition="inside" ms={0}>
|
||||
{prompts.map((prompt, i) => (
|
||||
<ListItem
|
||||
fontSize="sm"
|
||||
key={`${prompt}.${i}`}
|
||||
sx={listItemStyles}
|
||||
>
|
||||
<Text as="span">{prompt}</Text>
|
||||
</ListItem>
|
||||
))}
|
||||
</OrderedList>
|
||||
</ScrollableContent>
|
||||
{isLoading && (
|
||||
<Flex
|
||||
pos="absolute"
|
||||
w="full"
|
||||
h="full"
|
||||
top={0}
|
||||
insetInlineStart={0}
|
||||
layerStyle="second"
|
||||
opacity={0.7}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Spinner />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamDynamicPromptsPreview);
|
@ -0,0 +1,60 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
SeedBehaviour,
|
||||
seedBehaviourChanged,
|
||||
} from '../store/dynamicPromptsSlice';
|
||||
import IAIMantineSelectItemWithDescription from 'common/components/IAIMantineSelectItemWithDescription';
|
||||
|
||||
type Item = {
|
||||
label: string;
|
||||
value: SeedBehaviour;
|
||||
description: string;
|
||||
};
|
||||
|
||||
const ParamDynamicPromptsSeedBehaviour = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const seedBehaviour = useAppSelector(
|
||||
(state) => state.dynamicPrompts.seedBehaviour
|
||||
);
|
||||
|
||||
const data = useMemo<Item[]>(() => {
|
||||
return [
|
||||
{
|
||||
value: 'PER_ITERATION',
|
||||
label: t('dynamicPrompts.seedBehaviour.perIterationLabel'),
|
||||
description: t('dynamicPrompts.seedBehaviour.perIterationDesc'),
|
||||
},
|
||||
{
|
||||
value: 'PER_PROMPT',
|
||||
label: t('dynamicPrompts.seedBehaviour.perPromptLabel'),
|
||||
description: t('dynamicPrompts.seedBehaviour.perPromptDesc'),
|
||||
},
|
||||
];
|
||||
}, [t]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
dispatch(seedBehaviourChanged(v as SeedBehaviour));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIMantineSelect
|
||||
label={t('dynamicPrompts.seedBehaviour.label')}
|
||||
value={seedBehaviour}
|
||||
data={data}
|
||||
itemComponent={IAIMantineSelectItemWithDescription}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamDynamicPromptsSeedBehaviour);
|
@ -0,0 +1,4 @@
|
||||
import { initialDynamicPromptsState } from './dynamicPromptsSlice';
|
||||
|
||||
export const dynamicPromptsPersistDenylist: (keyof typeof initialDynamicPromptsState)[] =
|
||||
['prompts'];
|
@ -1,15 +1,24 @@
|
||||
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
export type SeedBehaviour = 'PER_ITERATION' | 'PER_PROMPT';
|
||||
export interface DynamicPromptsState {
|
||||
isEnabled: boolean;
|
||||
maxPrompts: number;
|
||||
combinatorial: boolean;
|
||||
prompts: string[];
|
||||
parsingError: string | undefined | null;
|
||||
isError: boolean;
|
||||
isLoading: boolean;
|
||||
seedBehaviour: SeedBehaviour;
|
||||
}
|
||||
|
||||
export const initialDynamicPromptsState: DynamicPromptsState = {
|
||||
isEnabled: false,
|
||||
maxPrompts: 100,
|
||||
combinatorial: true,
|
||||
prompts: [],
|
||||
parsingError: undefined,
|
||||
isError: false,
|
||||
isLoading: false,
|
||||
seedBehaviour: 'PER_ITERATION',
|
||||
};
|
||||
|
||||
const initialState: DynamicPromptsState = initialDynamicPromptsState;
|
||||
@ -27,17 +36,33 @@ export const dynamicPromptsSlice = createSlice({
|
||||
combinatorialToggled: (state) => {
|
||||
state.combinatorial = !state.combinatorial;
|
||||
},
|
||||
isEnabledToggled: (state) => {
|
||||
state.isEnabled = !state.isEnabled;
|
||||
promptsChanged: (state, action: PayloadAction<string[]>) => {
|
||||
state.prompts = action.payload;
|
||||
},
|
||||
parsingErrorChanged: (state, action: PayloadAction<string | undefined>) => {
|
||||
state.parsingError = action.payload;
|
||||
},
|
||||
isErrorChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.isError = action.payload;
|
||||
},
|
||||
isLoadingChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.isLoading = action.payload;
|
||||
},
|
||||
seedBehaviourChanged: (state, action: PayloadAction<SeedBehaviour>) => {
|
||||
state.seedBehaviour = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
isEnabledToggled,
|
||||
maxPromptsChanged,
|
||||
maxPromptsReset,
|
||||
combinatorialToggled,
|
||||
promptsChanged,
|
||||
parsingErrorChanged,
|
||||
isErrorChanged,
|
||||
isLoadingChanged,
|
||||
seedBehaviourChanged,
|
||||
} = dynamicPromptsSlice.actions;
|
||||
|
||||
export default dynamicPromptsSlice.reducer;
|
||||
|
@ -7,19 +7,17 @@ import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSe
|
||||
import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip';
|
||||
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||
|
||||
const selector = createSelector(
|
||||
[stateSelector],
|
||||
({ gallery, system }) => {
|
||||
({ gallery }) => {
|
||||
const { autoAddBoardId, autoAssignBoardOnClick } = gallery;
|
||||
const { isProcessing } = system;
|
||||
|
||||
return {
|
||||
autoAddBoardId,
|
||||
autoAssignBoardOnClick,
|
||||
isProcessing,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
@ -28,8 +26,7 @@ const selector = createSelector(
|
||||
const BoardAutoAddSelect = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { autoAddBoardId, autoAssignBoardOnClick, isProcessing } =
|
||||
useAppSelector(selector);
|
||||
const { autoAddBoardId, autoAssignBoardOnClick } = useAppSelector(selector);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { boards, hasBoards } = useListAllBoardsQuery(undefined, {
|
||||
selectFromResult: ({ data }) => {
|
||||
@ -73,7 +70,7 @@ const BoardAutoAddSelect = () => {
|
||||
data={boards}
|
||||
nothingFound={t('boards.noMatching')}
|
||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||
disabled={!hasBoards || autoAssignBoardOnClick || isProcessing}
|
||||
disabled={!hasBoards || autoAssignBoardOnClick}
|
||||
filter={(value, item: SelectItem) =>
|
||||
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
||||
item.value.toLowerCase().includes(value.toLowerCase().trim())
|
||||
|
@ -2,6 +2,7 @@ import { MenuGroup, MenuItem, MenuList } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import {
|
||||
IAIContextMenu,
|
||||
IAIContextMenuProps,
|
||||
@ -9,14 +10,13 @@ import {
|
||||
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
|
||||
import { BoardId } from 'features/gallery/store/types';
|
||||
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaPlus } from 'react-icons/fa';
|
||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||
import { BoardDTO } from 'services/api/types';
|
||||
import { menuListMotionProps } from 'theme/components/menu';
|
||||
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
|
||||
import NoBoardContextMenuItems from './NoBoardContextMenuItems';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
board?: BoardDTO;
|
||||
@ -37,19 +37,17 @@ const BoardContextMenu = ({
|
||||
() =>
|
||||
createSelector(
|
||||
stateSelector,
|
||||
({ gallery, system }) => {
|
||||
({ gallery }) => {
|
||||
const isAutoAdd = gallery.autoAddBoardId === board_id;
|
||||
const isProcessing = system.isProcessing;
|
||||
const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
|
||||
return { isAutoAdd, isProcessing, autoAssignBoardOnClick };
|
||||
return { isAutoAdd, autoAssignBoardOnClick };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
),
|
||||
[board_id]
|
||||
);
|
||||
|
||||
const { isAutoAdd, isProcessing, autoAssignBoardOnClick } =
|
||||
useAppSelector(selector);
|
||||
const { isAutoAdd, autoAssignBoardOnClick } = useAppSelector(selector);
|
||||
const boardName = useBoardName(board_id);
|
||||
|
||||
const handleSetAutoAdd = useCallback(() => {
|
||||
@ -78,7 +76,7 @@ const BoardContextMenu = ({
|
||||
<MenuGroup title={boardName}>
|
||||
<MenuItem
|
||||
icon={<FaPlus />}
|
||||
isDisabled={isAutoAdd || isProcessing || autoAssignBoardOnClick}
|
||||
isDisabled={isAutoAdd || autoAssignBoardOnClick}
|
||||
onClick={handleSetAutoAdd}
|
||||
>
|
||||
{t('boards.menuItemAutoAdd')}
|
||||
|
@ -16,6 +16,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import IAIDroppable from 'common/components/IAIDroppable';
|
||||
import SelectionOverlay from 'common/components/SelectionOverlay';
|
||||
import { AddToBoardDropData } from 'features/dnd/types';
|
||||
import {
|
||||
autoAddBoardIdChanged,
|
||||
boardIdSelected,
|
||||
@ -31,7 +32,6 @@ import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import { BoardDTO } from 'services/api/types';
|
||||
import AutoAddIcon from '../AutoAddIcon';
|
||||
import BoardContextMenu from '../BoardContextMenu';
|
||||
import { AddToBoardDropData } from 'features/dnd/types';
|
||||
|
||||
interface GalleryBoardProps {
|
||||
board: BoardDTO;
|
||||
@ -49,16 +49,14 @@ const GalleryBoard = ({
|
||||
() =>
|
||||
createSelector(
|
||||
stateSelector,
|
||||
({ gallery, system }) => {
|
||||
({ gallery }) => {
|
||||
const isSelectedForAutoAdd =
|
||||
board.board_id === gallery.autoAddBoardId;
|
||||
const autoAssignBoardOnClick = gallery.autoAssignBoardOnClick;
|
||||
const isProcessing = system.isProcessing;
|
||||
|
||||
return {
|
||||
isSelectedForAutoAdd,
|
||||
autoAssignBoardOnClick,
|
||||
isProcessing,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
@ -66,7 +64,7 @@ const GalleryBoard = ({
|
||||
[board.board_id]
|
||||
);
|
||||
|
||||
const { isSelectedForAutoAdd, autoAssignBoardOnClick, isProcessing } =
|
||||
const { isSelectedForAutoAdd, autoAssignBoardOnClick } =
|
||||
useAppSelector(selector);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const handleMouseOver = useCallback(() => {
|
||||
@ -96,10 +94,10 @@ const GalleryBoard = ({
|
||||
|
||||
const handleSelectBoard = useCallback(() => {
|
||||
dispatch(boardIdSelected(board_id));
|
||||
if (autoAssignBoardOnClick && !isProcessing) {
|
||||
if (autoAssignBoardOnClick) {
|
||||
dispatch(autoAddBoardIdChanged(board_id));
|
||||
}
|
||||
}, [board_id, autoAssignBoardOnClick, isProcessing, dispatch]);
|
||||
}, [board_id, autoAssignBoardOnClick, dispatch]);
|
||||
|
||||
const [updateBoard, { isLoading: isUpdateBoardLoading }] =
|
||||
useUpdateBoardMutation();
|
||||
|
@ -22,25 +22,23 @@ interface Props {
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
({ gallery, system }) => {
|
||||
({ gallery }) => {
|
||||
const { autoAddBoardId, autoAssignBoardOnClick } = gallery;
|
||||
const { isProcessing } = system;
|
||||
return { autoAddBoardId, autoAssignBoardOnClick, isProcessing };
|
||||
return { autoAddBoardId, autoAssignBoardOnClick };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const NoBoardBoard = memo(({ isSelected }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { autoAddBoardId, autoAssignBoardOnClick, isProcessing } =
|
||||
useAppSelector(selector);
|
||||
const { autoAddBoardId, autoAssignBoardOnClick } = useAppSelector(selector);
|
||||
const boardName = useBoardName('none');
|
||||
const handleSelectBoard = useCallback(() => {
|
||||
dispatch(boardIdSelected('none'));
|
||||
if (autoAssignBoardOnClick && !isProcessing) {
|
||||
if (autoAssignBoardOnClick) {
|
||||
dispatch(autoAddBoardIdChanged('none'));
|
||||
}
|
||||
}, [dispatch, autoAssignBoardOnClick, isProcessing]);
|
||||
}, [dispatch, autoAssignBoardOnClick]);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
const handleMouseOver = useCallback(() => {
|
||||
|
@ -21,6 +21,7 @@ import { workflowLoadRequested } from 'features/nodes/store/actions';
|
||||
import ParamUpscalePopover from 'features/parameters/components/Parameters/Upscale/ParamUpscaleSettings';
|
||||
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||
import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import {
|
||||
@ -36,9 +37,8 @@ import {
|
||||
FaHourglassHalf,
|
||||
FaQuoteRight,
|
||||
FaSeedling,
|
||||
FaShareAlt,
|
||||
} from 'react-icons/fa';
|
||||
import { MdDeviceHub } from 'react-icons/md';
|
||||
import { FaCircleNodes, FaEllipsis } from 'react-icons/fa6';
|
||||
import {
|
||||
useGetImageDTOQuery,
|
||||
useGetImageMetadataFromFileQuery,
|
||||
@ -50,8 +50,7 @@ import SingleSelectionMenuItems from '../ImageContextMenu/SingleSelectionMenuIte
|
||||
const currentImageButtonsSelector = createSelector(
|
||||
[stateSelector, activeTabNameSelector],
|
||||
({ gallery, system, ui, config }, activeTabName) => {
|
||||
const { isProcessing, isConnected, shouldConfirmOnDelete, progressImage } =
|
||||
system;
|
||||
const { isConnected, shouldConfirmOnDelete, denoiseProgress } = system;
|
||||
|
||||
const {
|
||||
shouldShowImageDetails,
|
||||
@ -64,11 +63,10 @@ const currentImageButtonsSelector = createSelector(
|
||||
const lastSelectedImage = gallery.selection[gallery.selection.length - 1];
|
||||
|
||||
return {
|
||||
canDeleteImage: isConnected && !isProcessing,
|
||||
shouldConfirmOnDelete,
|
||||
isProcessing,
|
||||
isConnected,
|
||||
shouldDisableToolbarButtons: Boolean(progressImage) || !lastSelectedImage,
|
||||
shouldDisableToolbarButtons:
|
||||
Boolean(denoiseProgress?.progress_image) || !lastSelectedImage,
|
||||
shouldShowImageDetails,
|
||||
activeTabName,
|
||||
shouldHidePreview,
|
||||
@ -89,7 +87,6 @@ type CurrentImageButtonsProps = FlexProps;
|
||||
const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const {
|
||||
isProcessing,
|
||||
isConnected,
|
||||
shouldDisableToolbarButtons,
|
||||
shouldShowImageDetails,
|
||||
@ -99,7 +96,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
} = useAppSelector(currentImageButtonsSelector);
|
||||
|
||||
const isUpscalingEnabled = useFeatureStatus('upscaling').isFeatureEnabled;
|
||||
|
||||
const isQueueMutationInProgress = useIsQueueMutationInProgress();
|
||||
const toaster = useAppToaster();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -202,19 +199,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
{
|
||||
enabled: () =>
|
||||
Boolean(
|
||||
isUpscalingEnabled &&
|
||||
!shouldDisableToolbarButtons &&
|
||||
isConnected &&
|
||||
!isProcessing
|
||||
isUpscalingEnabled && !shouldDisableToolbarButtons && isConnected
|
||||
),
|
||||
},
|
||||
[
|
||||
isUpscalingEnabled,
|
||||
imageDTO,
|
||||
shouldDisableToolbarButtons,
|
||||
isConnected,
|
||||
isProcessing,
|
||||
]
|
||||
[isUpscalingEnabled, imageDTO, shouldDisableToolbarButtons, isConnected]
|
||||
);
|
||||
|
||||
const handleClickShowImageDetails = useCallback(
|
||||
@ -266,10 +254,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={IAIIconButton}
|
||||
aria-label={`${t('parameters.sendTo')}...`}
|
||||
tooltip={`${t('parameters.sendTo')}...`}
|
||||
aria-label={t('parameters.imageActions')}
|
||||
tooltip={t('parameters.imageActions')}
|
||||
isDisabled={!imageDTO}
|
||||
icon={<FaShareAlt />}
|
||||
icon={<FaEllipsis />}
|
||||
/>
|
||||
<MenuList motionProps={menuListMotionProps}>
|
||||
{imageDTO && <SingleSelectionMenuItems imageDTO={imageDTO} />}
|
||||
@ -280,7 +268,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
<ButtonGroup isAttached={true} isDisabled={shouldDisableToolbarButtons}>
|
||||
<IAIIconButton
|
||||
isLoading={isLoading}
|
||||
icon={<MdDeviceHub />}
|
||||
icon={<FaCircleNodes />}
|
||||
tooltip={`${t('nodes.loadWorkflow')} (W)`}
|
||||
aria-label={`${t('nodes.loadWorkflow')} (W)`}
|
||||
isDisabled={!workflow}
|
||||
@ -313,15 +301,12 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
</ButtonGroup>
|
||||
|
||||
{isUpscalingEnabled && (
|
||||
<ButtonGroup
|
||||
isAttached={true}
|
||||
isDisabled={shouldDisableToolbarButtons}
|
||||
>
|
||||
<ButtonGroup isAttached={true} isDisabled={isQueueMutationInProgress}>
|
||||
{isUpscalingEnabled && <ParamUpscalePopover imageDTO={imageDTO} />}
|
||||
</ButtonGroup>
|
||||
)}
|
||||
|
||||
<ButtonGroup isAttached={true} isDisabled={shouldDisableToolbarButtons}>
|
||||
<ButtonGroup isAttached={true}>
|
||||
<IAIIconButton
|
||||
icon={<FaCode />}
|
||||
tooltip={`${t('parameters.info')} (I)`}
|
||||
@ -342,10 +327,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup isAttached={true}>
|
||||
<DeleteImageButton
|
||||
onClick={handleDelete}
|
||||
isDisabled={shouldDisableToolbarButtons}
|
||||
/>
|
||||
<DeleteImageButton onClick={handleDelete} />
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
</>
|
||||
|
@ -29,12 +29,12 @@ export const imagesSelector = createSelector(
|
||||
shouldHidePreview,
|
||||
shouldShowProgressInViewer,
|
||||
} = ui;
|
||||
const { progressImage, shouldAntialiasProgressImage } = system;
|
||||
const { denoiseProgress, shouldAntialiasProgressImage } = system;
|
||||
return {
|
||||
shouldShowImageDetails,
|
||||
shouldHidePreview,
|
||||
imageName: lastSelectedImage?.image_name,
|
||||
progressImage,
|
||||
denoiseProgress,
|
||||
shouldShowProgressInViewer,
|
||||
shouldAntialiasProgressImage,
|
||||
};
|
||||
@ -50,7 +50,7 @@ const CurrentImagePreview = () => {
|
||||
const {
|
||||
shouldShowImageDetails,
|
||||
imageName,
|
||||
progressImage,
|
||||
denoiseProgress,
|
||||
shouldShowProgressInViewer,
|
||||
shouldAntialiasProgressImage,
|
||||
} = useAppSelector(imagesSelector);
|
||||
@ -143,11 +143,11 @@ const CurrentImagePreview = () => {
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{progressImage && shouldShowProgressInViewer ? (
|
||||
{denoiseProgress?.progress_image && shouldShowProgressInViewer ? (
|
||||
<Image
|
||||
src={progressImage.dataURL}
|
||||
width={progressImage.width}
|
||||
height={progressImage.height}
|
||||
src={denoiseProgress.progress_image.dataURL}
|
||||
width={denoiseProgress.progress_image.width}
|
||||
height={denoiseProgress.progress_image.height}
|
||||
draggable={false}
|
||||
sx={{
|
||||
objectFit: 'contain',
|
||||
|
@ -16,6 +16,7 @@ import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard';
|
||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
FaAsterisk,
|
||||
@ -28,7 +29,8 @@ import {
|
||||
FaShare,
|
||||
FaTrash,
|
||||
} from 'react-icons/fa';
|
||||
import { MdDeviceHub, MdStar, MdStarBorder } from 'react-icons/md';
|
||||
import { FaCircleNodes } from 'react-icons/fa6';
|
||||
import { MdStar, MdStarBorder } from 'react-icons/md';
|
||||
import {
|
||||
useGetImageMetadataFromFileQuery,
|
||||
useStarImagesMutation,
|
||||
@ -37,7 +39,6 @@ import {
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
import { configSelector } from '../../../system/store/configSelectors';
|
||||
import { sentImageToCanvas, sentImageToImg2Img } from '../../store/actions';
|
||||
import { flushSync } from 'react-dom';
|
||||
|
||||
type SingleSelectionMenuItemsProps = {
|
||||
imageDTO: ImageDTO;
|
||||
@ -180,7 +181,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
||||
{t('parameters.downloadImage')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={isLoading ? <SpinnerIcon /> : <MdDeviceHub />}
|
||||
icon={isLoading ? <SpinnerIcon /> : <FaCircleNodes />}
|
||||
onClickCapture={handleLoadWorkflow}
|
||||
isDisabled={isLoading || !workflow}
|
||||
>
|
||||
|
@ -103,7 +103,7 @@ const ImageMetadataActions = (props: Props) => {
|
||||
)}
|
||||
{metadata.negative_prompt && (
|
||||
<ImageMetadataItem
|
||||
label={t('metadata.NegativePrompt')}
|
||||
label={t('metadata.negativePrompt')}
|
||||
labelPosition="top"
|
||||
value={metadata.negative_prompt}
|
||||
onClick={handleRecallNegativePrompt}
|
||||
|
@ -96,7 +96,7 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
|
||||
{workflow ? (
|
||||
<DataViewer data={workflow} label={t('metadata.workflow')} />
|
||||
) : (
|
||||
<IAINoContentFallback label={t('metadata.noWorkFlow')} />
|
||||
<IAINoContentFallback label={t('nodes.noWorkflow')} />
|
||||
)}
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
|
@ -96,7 +96,7 @@ const AddNodePopover = () => {
|
||||
(nodeType: AnyInvocationType) => {
|
||||
const invocation = buildInvocation(nodeType);
|
||||
if (!invocation) {
|
||||
const errorMessage = t('nodes.unknownInvocation', {
|
||||
const errorMessage = t('nodes.unknownNode', {
|
||||
nodeType: nodeType,
|
||||
});
|
||||
toaster({
|
||||
|
@ -16,7 +16,7 @@ const selector = createSelector(stateSelector, ({ system, gallery }) => {
|
||||
|
||||
return {
|
||||
imageDTO,
|
||||
progressImage: system.progressImage,
|
||||
progressImage: system.denoiseProgress?.progress_image,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -27,7 +27,7 @@ const EmbedWorkflowCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||
|
||||
return (
|
||||
<FormControl as={Flex} sx={{ alignItems: 'center', gap: 2, w: 'auto' }}>
|
||||
<FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>Embed Workflow</FormLabel>
|
||||
<FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>Workflow</FormLabel>
|
||||
<Checkbox
|
||||
className="nopan"
|
||||
size="sm"
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { Flex, Grid, GridItem } from '@chakra-ui/react';
|
||||
import { useAnyOrDirectInputFieldNames } from 'features/nodes/hooks/useAnyOrDirectInputFieldNames';
|
||||
import { useConnectionInputFieldNames } from 'features/nodes/hooks/useConnectionInputFieldNames';
|
||||
import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames';
|
||||
import { memo } from 'react';
|
||||
import NodeWrapper from '../common/NodeWrapper';
|
||||
import InvocationNodeFooter from './InvocationNodeFooter';
|
||||
import InvocationNodeHeader from './InvocationNodeHeader';
|
||||
import NodeWrapper from '../common/NodeWrapper';
|
||||
import OutputField from './fields/OutputField';
|
||||
import InputField from './fields/InputField';
|
||||
import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames';
|
||||
import { useWithFooter } from 'features/nodes/hooks/useWithFooter';
|
||||
import { useConnectionInputFieldNames } from 'features/nodes/hooks/useConnectionInputFieldNames';
|
||||
import { useAnyOrDirectInputFieldNames } from 'features/nodes/hooks/useAnyOrDirectInputFieldNames';
|
||||
import OutputField from './fields/OutputField';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
@ -22,7 +21,6 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
|
||||
const inputConnectionFieldNames = useConnectionInputFieldNames(nodeId);
|
||||
const inputAnyOrDirectFieldNames = useAnyOrDirectInputFieldNames(nodeId);
|
||||
const outputFieldNames = useOutputFieldNames(nodeId);
|
||||
const withFooter = useWithFooter(nodeId);
|
||||
|
||||
return (
|
||||
<NodeWrapper nodeId={nodeId} selected={selected}>
|
||||
@ -43,7 +41,7 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
|
||||
h: 'full',
|
||||
py: 2,
|
||||
gap: 1,
|
||||
borderBottomRadius: withFooter ? 0 : 'base',
|
||||
borderBottomRadius: 0,
|
||||
}}
|
||||
>
|
||||
<Flex sx={{ flexDir: 'column', px: 2, w: 'full', h: 'full' }}>
|
||||
@ -76,7 +74,7 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
{withFooter && <InvocationNodeFooter nodeId={nodeId} />}
|
||||
<InvocationNodeFooter nodeId={nodeId} />
|
||||
</>
|
||||
)}
|
||||
</NodeWrapper>
|
||||
|
@ -3,12 +3,15 @@ import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||
import { memo } from 'react';
|
||||
import EmbedWorkflowCheckbox from './EmbedWorkflowCheckbox';
|
||||
import SaveToGalleryCheckbox from './SaveToGalleryCheckbox';
|
||||
import UseCacheCheckbox from './UseCacheCheckbox';
|
||||
import { useHasImageOutput } from 'features/nodes/hooks/useHasImageOutput';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
};
|
||||
|
||||
const InvocationNodeFooter = ({ nodeId }: Props) => {
|
||||
const hasImageOutput = useHasImageOutput(nodeId);
|
||||
return (
|
||||
<Flex
|
||||
className={DRAG_HANDLE_CLASSNAME}
|
||||
@ -22,8 +25,9 @@ const InvocationNodeFooter = ({ nodeId }: Props) => {
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<EmbedWorkflowCheckbox nodeId={nodeId} />
|
||||
<SaveToGalleryCheckbox nodeId={nodeId} />
|
||||
{hasImageOutput && <EmbedWorkflowCheckbox nodeId={nodeId} />}
|
||||
<UseCacheCheckbox nodeId={nodeId} />
|
||||
{hasImageOutput && <SaveToGalleryCheckbox nodeId={nodeId} />}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,35 @@
|
||||
import { Checkbox, Flex, FormControl, FormLabel } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useUseCache } from 'features/nodes/hooks/useUseCache';
|
||||
import { nodeUseCacheChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
|
||||
const UseCacheCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const useCache = useUseCache(nodeId);
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(
|
||||
nodeUseCacheChanged({
|
||||
nodeId,
|
||||
useCache: e.target.checked,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, nodeId]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl as={Flex} sx={{ alignItems: 'center', gap: 2, w: 'auto' }}>
|
||||
<FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>Use Cache</FormLabel>
|
||||
<Checkbox
|
||||
className="nopan"
|
||||
size="sm"
|
||||
onChange={handleChange}
|
||||
isChecked={useCache}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(UseCacheCheckbox);
|
@ -38,7 +38,7 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const [localTitle, setLocalTitle] = useState(
|
||||
label || fieldTemplateTitle || t('nodes.unknownFeild')
|
||||
label || fieldTemplateTitle || t('nodes.unknownField')
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
|
@ -114,7 +114,7 @@ const NodeWrapper = (props: NodeWrapperProps) => {
|
||||
borderRadius: 'md',
|
||||
pointerEvents: 'none',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: 'normal',
|
||||
transitionDuration: '0.1s',
|
||||
opacity: 0.7,
|
||||
shadow: isInProgress ? inProgressShadow : undefined,
|
||||
zIndex: -1,
|
||||
|
@ -1,15 +0,0 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton';
|
||||
import InvokeButton from 'features/parameters/components/ProcessButtons/InvokeButton';
|
||||
import { memo } from 'react';
|
||||
|
||||
const WorkflowEditorControls = () => {
|
||||
return (
|
||||
<Flex layerStyle="first" sx={{ gap: 2, borderRadius: 'base', p: 2 }}>
|
||||
<InvokeButton />
|
||||
<CancelButton />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(WorkflowEditorControls);
|
@ -1,5 +1,5 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons';
|
||||
import QueueControls from 'features/queue/components/QueueControls';
|
||||
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
||||
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
||||
import { memo, useCallback, useRef, useState } from 'react';
|
||||
@ -11,6 +11,7 @@ import {
|
||||
import 'reactflow/dist/style.css';
|
||||
import InspectorPanel from './inspector/InspectorPanel';
|
||||
import WorkflowPanel from './workflow/WorkflowPanel';
|
||||
import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations';
|
||||
|
||||
const NodeEditorPanelGroup = () => {
|
||||
const [isTopPanelCollapsed, setIsTopPanelCollapsed] = useState(false);
|
||||
@ -26,7 +27,21 @@ const NodeEditorPanelGroup = () => {
|
||||
|
||||
return (
|
||||
<Flex sx={{ flexDir: 'column', gap: 2, height: '100%', width: '100%' }}>
|
||||
<ProcessButtons />
|
||||
<QueueControls />
|
||||
<Flex
|
||||
layerStyle="first"
|
||||
sx={{
|
||||
w: 'full',
|
||||
position: 'relative',
|
||||
borderRadius: 'base',
|
||||
p: 2,
|
||||
pb: 3,
|
||||
gap: 2,
|
||||
flexDir: 'column',
|
||||
}}
|
||||
>
|
||||
<ParamIterations asSlider />
|
||||
</Flex>
|
||||
<PanelGroup
|
||||
ref={panelGroupRef}
|
||||
id="workflow-panel-group"
|
||||
|
@ -1,13 +1,18 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { Box, Flex, StyleProps } from '@chakra-ui/react';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import { PropsWithChildren, memo } from 'react';
|
||||
|
||||
const ScrollableContent = (props: PropsWithChildren) => {
|
||||
type Props = PropsWithChildren & {
|
||||
maxHeight?: StyleProps['maxHeight'];
|
||||
};
|
||||
|
||||
const ScrollableContent = ({ children, maxHeight }: Props) => {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
maxHeight,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
@ -35,7 +40,7 @@ const ScrollableContent = (props: PropsWithChildren) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
{children}
|
||||
</OverlayScrollbarsComponent>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
@ -83,7 +83,7 @@ const InspectorOutputsTab = () => {
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<DataViewer data={nes.outputs} label={t('nodes.nodesOutputs')} />
|
||||
<DataViewer data={nes.outputs} label={t('nodes.nodeOutputs')} />
|
||||
)}
|
||||
</Flex>
|
||||
</ScrollableContent>
|
||||
|
@ -38,7 +38,7 @@ const NodeTemplateInspector = () => {
|
||||
);
|
||||
}
|
||||
|
||||
return <DataViewer data={template} label={t('nodes.NodeTemplate')} />;
|
||||
return <DataViewer data={template} label={t('nodes.nodeTemplate')} />;
|
||||
};
|
||||
|
||||
export default memo(NodeTemplateInspector);
|
||||
|
@ -146,6 +146,7 @@ export const useBuildNodeData = () => {
|
||||
isIntermediate: true,
|
||||
inputs,
|
||||
outputs,
|
||||
useCache: template.useCache,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,29 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { useMemo } from 'react';
|
||||
import { isInvocationNode } from '../types/types';
|
||||
|
||||
export const useUseCache = (nodeId: string) => {
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createSelector(
|
||||
stateSelector,
|
||||
({ nodes }) => {
|
||||
const node = nodes.nodes.find((node) => node.id === nodeId);
|
||||
if (!isInvocationNode(node)) {
|
||||
return false;
|
||||
}
|
||||
// cast to boolean to support older workflows that didn't have useCache
|
||||
// TODO: handle this better somehow
|
||||
return node.data.useCache;
|
||||
},
|
||||
defaultSelectorOptions
|
||||
),
|
||||
[nodeId]
|
||||
);
|
||||
|
||||
const useCache = useAppSelector(selector);
|
||||
return useCache;
|
||||
};
|
@ -7,7 +7,7 @@ import { useMemo } from 'react';
|
||||
import { FOOTER_FIELDS } from '../types/constants';
|
||||
import { isInvocationNode } from '../types/types';
|
||||
|
||||
export const useWithFooter = (nodeId: string) => {
|
||||
export const useHasImageOutputs = (nodeId: string) => {
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createSelector(
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
appSocketInvocationComplete,
|
||||
appSocketInvocationError,
|
||||
appSocketInvocationStarted,
|
||||
appSocketQueueItemStatusChanged,
|
||||
} from 'services/events/actions';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { DRAG_HANDLE_CLASSNAME } from '../types/constants';
|
||||
@ -261,6 +262,20 @@ const nodesSlice = createSlice({
|
||||
}
|
||||
node.data.embedWorkflow = embedWorkflow;
|
||||
},
|
||||
nodeUseCacheChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ nodeId: string; useCache: boolean }>
|
||||
) => {
|
||||
const { nodeId, useCache } = action.payload;
|
||||
const nodeIndex = state.nodes.findIndex((n) => n.id === nodeId);
|
||||
|
||||
const node = state.nodes?.[nodeIndex];
|
||||
|
||||
if (!isInvocationNode(node)) {
|
||||
return;
|
||||
}
|
||||
node.data.useCache = useCache;
|
||||
},
|
||||
nodeIsIntermediateChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ nodeId: string; isIntermediate: boolean }>
|
||||
@ -847,6 +862,19 @@ const nodesSlice = createSlice({
|
||||
}
|
||||
});
|
||||
});
|
||||
builder.addCase(appSocketQueueItemStatusChanged, (state, action) => {
|
||||
if (
|
||||
['completed', 'canceled', 'failed'].includes(action.payload.data.status)
|
||||
) {
|
||||
forEach(state.nodeExecutionStates, (nes) => {
|
||||
nes.status = NodeStatus.PENDING;
|
||||
nes.error = null;
|
||||
nes.progress = null;
|
||||
nes.progressImage = null;
|
||||
// do not reset nes.outputs, this allows a user to inspect the output of a node across batches
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -912,6 +940,7 @@ export const {
|
||||
nodeIsIntermediateChanged,
|
||||
mouseOverNodeChanged,
|
||||
nodeExclusivelySelected,
|
||||
nodeUseCacheChanged,
|
||||
} = nodesSlice.actions;
|
||||
|
||||
export default nodesSlice.reducer;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { $store } from 'app/store/nanostores/store';
|
||||
import {
|
||||
SchedulerParam,
|
||||
zBaseModel,
|
||||
@ -7,7 +8,8 @@ import {
|
||||
zSDXLRefinerModel,
|
||||
zScheduler,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
import { keyBy } from 'lodash-es';
|
||||
import i18n from 'i18next';
|
||||
import { has, keyBy } from 'lodash-es';
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import { RgbaColor } from 'react-colorful';
|
||||
import { Node } from 'reactflow';
|
||||
@ -20,7 +22,6 @@ import {
|
||||
import { O } from 'ts-toolbelt';
|
||||
import { JsonObject } from 'type-fest';
|
||||
import { z } from 'zod';
|
||||
import i18n from 'i18next';
|
||||
|
||||
export type NonNullableGraph = O.Required<Graph, 'nodes' | 'edges'>;
|
||||
|
||||
@ -57,6 +58,10 @@ export type InvocationTemplate = {
|
||||
* The invocation's version.
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* Whether or not this node should use the cache
|
||||
*/
|
||||
useCache: boolean;
|
||||
};
|
||||
|
||||
export type FieldUIConfig = {
|
||||
@ -1022,6 +1027,9 @@ export type InvocationSchemaExtra = {
|
||||
type: Omit<OpenAPIV3.SchemaObject, 'default'> & {
|
||||
default: AnyInvocationType;
|
||||
};
|
||||
use_cache: Omit<OpenAPIV3.SchemaObject, 'default'> & {
|
||||
default: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -1185,9 +1193,37 @@ export const zInvocationNodeData = z.object({
|
||||
version: zSemVer.optional(),
|
||||
});
|
||||
|
||||
export const zInvocationNodeDataV2 = z.preprocess(
|
||||
(arg) => {
|
||||
try {
|
||||
const data = zInvocationNodeData.parse(arg);
|
||||
if (!has(data, 'useCache')) {
|
||||
const nodeTemplates = $store.get()?.getState().nodes.nodeTemplates as
|
||||
| Record<string, InvocationTemplate>
|
||||
| undefined;
|
||||
|
||||
const template = nodeTemplates?.[data.type];
|
||||
|
||||
let useCache = true;
|
||||
if (template) {
|
||||
useCache = template.useCache;
|
||||
}
|
||||
|
||||
Object.assign(data, { useCache });
|
||||
}
|
||||
return data;
|
||||
} catch {
|
||||
return arg;
|
||||
}
|
||||
},
|
||||
zInvocationNodeData.extend({
|
||||
useCache: z.boolean(),
|
||||
})
|
||||
);
|
||||
|
||||
// Massage this to get better type safety while developing
|
||||
export type InvocationNodeData = Omit<
|
||||
z.infer<typeof zInvocationNodeData>,
|
||||
z.infer<typeof zInvocationNodeDataV2>,
|
||||
'type'
|
||||
> & {
|
||||
type: AnyInvocationType;
|
||||
@ -1215,7 +1251,7 @@ const zDimension = z.number().gt(0).nullish();
|
||||
export const zWorkflowInvocationNode = z.object({
|
||||
id: z.string().trim().min(1),
|
||||
type: z.literal('invocation'),
|
||||
data: zInvocationNodeData,
|
||||
data: zInvocationNodeDataV2,
|
||||
width: zDimension,
|
||||
height: zDimension,
|
||||
position: zPosition,
|
||||
@ -1277,6 +1313,8 @@ export type WorkflowWarning = {
|
||||
data: JsonObject;
|
||||
};
|
||||
|
||||
const CURRENT_WORKFLOW_VERSION = '1.0.0';
|
||||
|
||||
export const zWorkflow = z.object({
|
||||
name: z.string().default(''),
|
||||
author: z.string().default(''),
|
||||
@ -1292,7 +1330,7 @@ export const zWorkflow = z.object({
|
||||
.object({
|
||||
version: zSemVer,
|
||||
})
|
||||
.default({ version: '1.0.0' }),
|
||||
.default({ version: CURRENT_WORKFLOW_VERSION }),
|
||||
});
|
||||
|
||||
export const zValidatedWorkflow = zWorkflow.transform((workflow) => {
|
||||
|
@ -1,209 +0,0 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { unset } from 'lodash-es';
|
||||
import {
|
||||
DynamicPromptInvocation,
|
||||
IterateInvocation,
|
||||
MetadataAccumulatorInvocation,
|
||||
NoiseInvocation,
|
||||
RandomIntInvocation,
|
||||
RangeOfSizeInvocation,
|
||||
} from 'services/api/types';
|
||||
import {
|
||||
DYNAMIC_PROMPT,
|
||||
ITERATE,
|
||||
METADATA_ACCUMULATOR,
|
||||
NOISE,
|
||||
POSITIVE_CONDITIONING,
|
||||
RANDOM_INT,
|
||||
RANGE_OF_SIZE,
|
||||
} from './constants';
|
||||
|
||||
export const addDynamicPromptsToGraph = (
|
||||
state: RootState,
|
||||
graph: NonNullableGraph
|
||||
): void => {
|
||||
const { positivePrompt, iterations, seed, shouldRandomizeSeed } =
|
||||
state.generation;
|
||||
|
||||
const {
|
||||
combinatorial,
|
||||
isEnabled: isDynamicPromptsEnabled,
|
||||
maxPrompts,
|
||||
} = state.dynamicPrompts;
|
||||
|
||||
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
|
||||
| MetadataAccumulatorInvocation
|
||||
| undefined;
|
||||
|
||||
if (isDynamicPromptsEnabled) {
|
||||
// iteration is handled via dynamic prompts
|
||||
unset(graph.nodes[POSITIVE_CONDITIONING], 'prompt');
|
||||
|
||||
const dynamicPromptNode: DynamicPromptInvocation = {
|
||||
id: DYNAMIC_PROMPT,
|
||||
type: 'dynamic_prompt',
|
||||
is_intermediate: true,
|
||||
max_prompts: combinatorial ? maxPrompts : iterations,
|
||||
combinatorial,
|
||||
prompt: positivePrompt,
|
||||
};
|
||||
|
||||
const iterateNode: IterateInvocation = {
|
||||
id: ITERATE,
|
||||
type: 'iterate',
|
||||
is_intermediate: true,
|
||||
};
|
||||
|
||||
graph.nodes[DYNAMIC_PROMPT] = dynamicPromptNode;
|
||||
graph.nodes[ITERATE] = iterateNode;
|
||||
|
||||
// connect dynamic prompts to compel nodes
|
||||
graph.edges.push(
|
||||
{
|
||||
source: {
|
||||
node_id: DYNAMIC_PROMPT,
|
||||
field: 'collection',
|
||||
},
|
||||
destination: {
|
||||
node_id: ITERATE,
|
||||
field: 'collection',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: POSITIVE_CONDITIONING,
|
||||
field: 'prompt',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// hook up positive prompt to metadata
|
||||
if (metadataAccumulator) {
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: METADATA_ACCUMULATOR,
|
||||
field: 'positive_prompt',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (shouldRandomizeSeed) {
|
||||
// Random int node to generate the starting seed
|
||||
const randomIntNode: RandomIntInvocation = {
|
||||
id: RANDOM_INT,
|
||||
type: 'rand_int',
|
||||
is_intermediate: true,
|
||||
};
|
||||
|
||||
graph.nodes[RANDOM_INT] = randomIntNode;
|
||||
|
||||
// Connect random int to the start of the range of size so the range starts on the random first seed
|
||||
graph.edges.push({
|
||||
source: { node_id: RANDOM_INT, field: 'value' },
|
||||
destination: { node_id: NOISE, field: 'seed' },
|
||||
});
|
||||
|
||||
if (metadataAccumulator) {
|
||||
graph.edges.push({
|
||||
source: { node_id: RANDOM_INT, field: 'value' },
|
||||
destination: { node_id: METADATA_ACCUMULATOR, field: 'seed' },
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// User specified seed, so set the start of the range of size to the seed
|
||||
(graph.nodes[NOISE] as NoiseInvocation).seed = seed;
|
||||
|
||||
// hook up seed to metadata
|
||||
if (metadataAccumulator) {
|
||||
metadataAccumulator.seed = seed;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no dynamic prompt - hook up positive prompt
|
||||
if (metadataAccumulator) {
|
||||
metadataAccumulator.positive_prompt = positivePrompt;
|
||||
}
|
||||
|
||||
const rangeOfSizeNode: RangeOfSizeInvocation = {
|
||||
id: RANGE_OF_SIZE,
|
||||
type: 'range_of_size',
|
||||
is_intermediate: true,
|
||||
size: iterations,
|
||||
step: 1,
|
||||
};
|
||||
|
||||
const iterateNode: IterateInvocation = {
|
||||
id: ITERATE,
|
||||
type: 'iterate',
|
||||
is_intermediate: true,
|
||||
};
|
||||
|
||||
graph.nodes[ITERATE] = iterateNode;
|
||||
graph.nodes[RANGE_OF_SIZE] = rangeOfSizeNode;
|
||||
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: RANGE_OF_SIZE,
|
||||
field: 'collection',
|
||||
},
|
||||
destination: {
|
||||
node_id: ITERATE,
|
||||
field: 'collection',
|
||||
},
|
||||
});
|
||||
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: NOISE,
|
||||
field: 'seed',
|
||||
},
|
||||
});
|
||||
|
||||
// hook up seed to metadata
|
||||
if (metadataAccumulator) {
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: METADATA_ACCUMULATOR,
|
||||
field: 'seed',
|
||||
},
|
||||
});
|
||||
}
|
||||
// handle seed
|
||||
if (shouldRandomizeSeed) {
|
||||
// Random int node to generate the starting seed
|
||||
const randomIntNode: RandomIntInvocation = {
|
||||
id: RANDOM_INT,
|
||||
type: 'rand_int',
|
||||
is_intermediate: true,
|
||||
};
|
||||
|
||||
graph.nodes[RANDOM_INT] = randomIntNode;
|
||||
|
||||
// Connect random int to the start of the range of size so the range starts on the random first seed
|
||||
graph.edges.push({
|
||||
source: { node_id: RANDOM_INT, field: 'value' },
|
||||
destination: { node_id: RANGE_OF_SIZE, field: 'start' },
|
||||
});
|
||||
} else {
|
||||
// User specified seed, so set the start of the range of size to the seed
|
||||
rangeOfSizeNode.start = seed;
|
||||
}
|
||||
}
|
||||
};
|
@ -1,46 +1,32 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import {
|
||||
ImageNSFWBlurInvocation,
|
||||
LatentsToImageInvocation,
|
||||
MetadataAccumulatorInvocation,
|
||||
} from 'services/api/types';
|
||||
import {
|
||||
LATENTS_TO_IMAGE,
|
||||
METADATA_ACCUMULATOR,
|
||||
NSFW_CHECKER,
|
||||
} from './constants';
|
||||
import { LATENTS_TO_IMAGE, NSFW_CHECKER } from './constants';
|
||||
|
||||
export const addNSFWCheckerToGraph = (
|
||||
state: RootState,
|
||||
graph: NonNullableGraph,
|
||||
nodeIdToAddTo = LATENTS_TO_IMAGE
|
||||
): void => {
|
||||
const activeTabName = activeTabNameSelector(state);
|
||||
|
||||
const is_intermediate =
|
||||
activeTabName === 'unifiedCanvas' ? !state.canvas.shouldAutoSave : false;
|
||||
|
||||
const nodeToAddTo = graph.nodes[nodeIdToAddTo] as
|
||||
| LatentsToImageInvocation
|
||||
| undefined;
|
||||
|
||||
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
|
||||
| MetadataAccumulatorInvocation
|
||||
| undefined;
|
||||
|
||||
if (!nodeToAddTo) {
|
||||
// something has gone terribly awry
|
||||
return;
|
||||
}
|
||||
|
||||
nodeToAddTo.is_intermediate = true;
|
||||
nodeToAddTo.use_cache = true;
|
||||
|
||||
const nsfwCheckerNode: ImageNSFWBlurInvocation = {
|
||||
id: NSFW_CHECKER,
|
||||
type: 'img_nsfw',
|
||||
is_intermediate,
|
||||
is_intermediate: true,
|
||||
};
|
||||
|
||||
graph.nodes[NSFW_CHECKER] = nsfwCheckerNode as ImageNSFWBlurInvocation;
|
||||
@ -54,17 +40,4 @@ export const addNSFWCheckerToGraph = (
|
||||
field: 'image',
|
||||
},
|
||||
});
|
||||
|
||||
if (metadataAccumulator) {
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: METADATA_ACCUMULATOR,
|
||||
field: 'metadata',
|
||||
},
|
||||
destination: {
|
||||
node_id: NSFW_CHECKER,
|
||||
field: 'metadata',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
SDXL_REFINER_POSITIVE_CONDITIONING,
|
||||
SDXL_REFINER_SEAMLESS,
|
||||
} from './constants';
|
||||
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
|
||||
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
||||
|
||||
export const addSDXLRefinerToGraph = (
|
||||
state: RootState,
|
||||
@ -78,8 +78,8 @@ export const addSDXLRefinerToGraph = (
|
||||
: SDXL_MODEL_LOADER;
|
||||
|
||||
// Construct Style Prompt
|
||||
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
|
||||
craftSDXLStylePrompt(state, true);
|
||||
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
|
||||
buildSDXLStylePrompts(state, true);
|
||||
|
||||
// Unplug SDXL Latents Generation To Latents To Image
|
||||
graph.edges = graph.edges.filter(
|
||||
@ -100,13 +100,13 @@ export const addSDXLRefinerToGraph = (
|
||||
graph.nodes[SDXL_REFINER_POSITIVE_CONDITIONING] = {
|
||||
type: 'sdxl_refiner_compel_prompt',
|
||||
id: SDXL_REFINER_POSITIVE_CONDITIONING,
|
||||
style: craftedPositiveStylePrompt,
|
||||
style: joinedPositiveStylePrompt,
|
||||
aesthetic_score: refinerPositiveAestheticScore,
|
||||
};
|
||||
graph.nodes[SDXL_REFINER_NEGATIVE_CONDITIONING] = {
|
||||
type: 'sdxl_refiner_compel_prompt',
|
||||
id: SDXL_REFINER_NEGATIVE_CONDITIONING,
|
||||
style: craftedNegativeStylePrompt,
|
||||
style: joinedNegativeStylePrompt,
|
||||
aesthetic_score: refinerNegativeAestheticScore,
|
||||
};
|
||||
graph.nodes[SDXL_REFINER_DENOISE_LATENTS] = {
|
||||
|
@ -0,0 +1,92 @@
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import {
|
||||
CANVAS_OUTPUT,
|
||||
LATENTS_TO_IMAGE,
|
||||
METADATA_ACCUMULATOR,
|
||||
NSFW_CHECKER,
|
||||
SAVE_IMAGE,
|
||||
WATERMARKER,
|
||||
} from './constants';
|
||||
import {
|
||||
MetadataAccumulatorInvocation,
|
||||
SaveImageInvocation,
|
||||
} from 'services/api/types';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
|
||||
/**
|
||||
* Set the `use_cache` field on the linear/canvas graph's final image output node to False.
|
||||
*/
|
||||
export const addSaveImageNode = (
|
||||
state: RootState,
|
||||
graph: NonNullableGraph
|
||||
): void => {
|
||||
const activeTabName = activeTabNameSelector(state);
|
||||
const is_intermediate =
|
||||
activeTabName === 'unifiedCanvas' ? !state.canvas.shouldAutoSave : false;
|
||||
|
||||
const saveImageNode: SaveImageInvocation = {
|
||||
id: SAVE_IMAGE,
|
||||
type: 'save_image',
|
||||
is_intermediate,
|
||||
use_cache: false,
|
||||
};
|
||||
|
||||
graph.nodes[SAVE_IMAGE] = saveImageNode;
|
||||
|
||||
const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as
|
||||
| MetadataAccumulatorInvocation
|
||||
| undefined;
|
||||
|
||||
if (metadataAccumulator) {
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: METADATA_ACCUMULATOR,
|
||||
field: 'metadata',
|
||||
},
|
||||
destination: {
|
||||
node_id: SAVE_IMAGE,
|
||||
field: 'metadata',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const destination = {
|
||||
node_id: SAVE_IMAGE,
|
||||
field: 'image',
|
||||
};
|
||||
|
||||
if (WATERMARKER in graph.nodes) {
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: WATERMARKER,
|
||||
field: 'image',
|
||||
},
|
||||
destination,
|
||||
});
|
||||
} else if (NSFW_CHECKER in graph.nodes) {
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: NSFW_CHECKER,
|
||||
field: 'image',
|
||||
},
|
||||
destination,
|
||||
});
|
||||
} else if (CANVAS_OUTPUT in graph.nodes) {
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: CANVAS_OUTPUT,
|
||||
field: 'image',
|
||||
},
|
||||
destination,
|
||||
});
|
||||
} else if (LATENTS_TO_IMAGE in graph.nodes) {
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: LATENTS_TO_IMAGE,
|
||||
field: 'image',
|
||||
},
|
||||
destination,
|
||||
});
|
||||
}
|
||||
};
|
@ -51,6 +51,7 @@ export const addWatermarkerToGraph = (
|
||||
|
||||
// no matter the situation, we want the l2i node to be intermediate
|
||||
nodeToAddTo.is_intermediate = true;
|
||||
nodeToAddTo.use_cache = true;
|
||||
|
||||
if (nsfwCheckerNode) {
|
||||
// if we are using NSFW checker, we need to "disable" it output by marking it intermediate,
|
||||
|
@ -1,7 +1,11 @@
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { ESRGANModelName } from 'features/parameters/store/postprocessingSlice';
|
||||
import { Graph, ESRGANInvocation } from 'services/api/types';
|
||||
import { REALESRGAN as ESRGAN } from './constants';
|
||||
import {
|
||||
Graph,
|
||||
ESRGANInvocation,
|
||||
SaveImageInvocation,
|
||||
} from 'services/api/types';
|
||||
import { REALESRGAN as ESRGAN, SAVE_IMAGE } from './constants';
|
||||
|
||||
type Arg = {
|
||||
image_name: string;
|
||||
@ -17,15 +21,33 @@ export const buildAdHocUpscaleGraph = ({
|
||||
type: 'esrgan',
|
||||
image: { image_name },
|
||||
model_name: esrganModelName,
|
||||
is_intermediate: false,
|
||||
is_intermediate: true,
|
||||
};
|
||||
|
||||
const saveImageNode: SaveImageInvocation = {
|
||||
id: SAVE_IMAGE,
|
||||
type: 'save_image',
|
||||
use_cache: false,
|
||||
};
|
||||
|
||||
const graph: NonNullableGraph = {
|
||||
id: `adhoc-esrgan-graph`,
|
||||
nodes: {
|
||||
[ESRGAN]: realesrganNode,
|
||||
[SAVE_IMAGE]: saveImageNode,
|
||||
},
|
||||
edges: [],
|
||||
edges: [
|
||||
{
|
||||
source: {
|
||||
node_id: ESRGAN,
|
||||
field: 'image',
|
||||
},
|
||||
destination: {
|
||||
node_id: SAVE_IMAGE,
|
||||
field: 'image',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return graph;
|
||||
|
@ -4,10 +4,10 @@ import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { initialGenerationState } from 'features/parameters/store/generationSlice';
|
||||
import { ImageDTO, ImageToLatentsInvocation } from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
import { addLoRAsToGraph } from './addLoRAsToGraph';
|
||||
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
|
||||
import { addSaveImageNode } from './addSaveImageNode';
|
||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||
@ -41,6 +41,7 @@ export const buildCanvasImageToImageGraph = (
|
||||
model,
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
seed,
|
||||
steps,
|
||||
img2imgStrength: strength,
|
||||
vaePrecision,
|
||||
@ -54,14 +55,10 @@ export const buildCanvasImageToImageGraph = (
|
||||
// The bounding box determines width and height, not the width and height params
|
||||
const { width, height } = state.canvas.boundingBoxDimensions;
|
||||
|
||||
const {
|
||||
scaledBoundingBoxDimensions,
|
||||
boundingBoxScaleMethod,
|
||||
shouldAutoSave,
|
||||
} = state.canvas;
|
||||
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
|
||||
|
||||
const fp32 = vaePrecision === 'fp32';
|
||||
|
||||
const is_intermediate = true;
|
||||
const isUsingScaledDimensions = ['auto', 'manual'].includes(
|
||||
boundingBoxScaleMethod
|
||||
);
|
||||
@ -93,32 +90,33 @@ export const buildCanvasImageToImageGraph = (
|
||||
[modelLoaderNodeId]: {
|
||||
type: 'main_model_loader',
|
||||
id: modelLoaderNodeId,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
model,
|
||||
},
|
||||
[CLIP_SKIP]: {
|
||||
type: 'clip_skip',
|
||||
id: CLIP_SKIP,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
skipped_layers: clipSkip,
|
||||
},
|
||||
[POSITIVE_CONDITIONING]: {
|
||||
type: 'compel',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
prompt: positivePrompt,
|
||||
},
|
||||
[NEGATIVE_CONDITIONING]: {
|
||||
type: 'compel',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
prompt: negativePrompt,
|
||||
},
|
||||
[NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
use_cpu,
|
||||
seed,
|
||||
width: !isUsingScaledDimensions
|
||||
? width
|
||||
: scaledBoundingBoxDimensions.width,
|
||||
@ -129,12 +127,12 @@ export const buildCanvasImageToImageGraph = (
|
||||
[IMAGE_TO_LATENTS]: {
|
||||
type: 'i2l',
|
||||
id: IMAGE_TO_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
},
|
||||
[DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
id: DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
@ -144,7 +142,7 @@ export const buildCanvasImageToImageGraph = (
|
||||
[CANVAS_OUTPUT]: {
|
||||
type: 'l2i',
|
||||
id: CANVAS_OUTPUT,
|
||||
is_intermediate: !shouldAutoSave,
|
||||
is_intermediate,
|
||||
},
|
||||
},
|
||||
edges: [
|
||||
@ -239,7 +237,7 @@ export const buildCanvasImageToImageGraph = (
|
||||
graph.nodes[IMG2IMG_RESIZE] = {
|
||||
id: IMG2IMG_RESIZE,
|
||||
type: 'img_resize',
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
image: initialImage,
|
||||
width: scaledBoundingBoxDimensions.width,
|
||||
height: scaledBoundingBoxDimensions.height,
|
||||
@ -247,13 +245,13 @@ export const buildCanvasImageToImageGraph = (
|
||||
graph.nodes[LATENTS_TO_IMAGE] = {
|
||||
id: LATENTS_TO_IMAGE,
|
||||
type: 'l2i',
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
};
|
||||
graph.nodes[CANVAS_OUTPUT] = {
|
||||
id: CANVAS_OUTPUT,
|
||||
type: 'img_resize',
|
||||
is_intermediate: !shouldAutoSave,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
@ -294,7 +292,7 @@ export const buildCanvasImageToImageGraph = (
|
||||
graph.nodes[CANVAS_OUTPUT] = {
|
||||
type: 'l2i',
|
||||
id: CANVAS_OUTPUT,
|
||||
is_intermediate: !shouldAutoSave,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
};
|
||||
|
||||
@ -323,10 +321,10 @@ export const buildCanvasImageToImageGraph = (
|
||||
height: !isUsingScaledDimensions
|
||||
? height
|
||||
: scaledBoundingBoxDimensions.height,
|
||||
positive_prompt: '', // set in addDynamicPromptsToGraph
|
||||
positive_prompt: positivePrompt,
|
||||
negative_prompt: negativePrompt,
|
||||
model,
|
||||
seed: 0, // set in addDynamicPromptsToGraph
|
||||
seed,
|
||||
steps,
|
||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||
scheduler,
|
||||
@ -338,17 +336,6 @@ export const buildCanvasImageToImageGraph = (
|
||||
init_image: initialImage.image_name,
|
||||
};
|
||||
|
||||
graph.edges.push({
|
||||
source: {
|
||||
node_id: METADATA_ACCUMULATOR,
|
||||
field: 'metadata',
|
||||
},
|
||||
destination: {
|
||||
node_id: CANVAS_OUTPUT,
|
||||
field: 'metadata',
|
||||
},
|
||||
});
|
||||
|
||||
// Add Seamless To Graph
|
||||
if (seamlessXAxis || seamlessYAxis) {
|
||||
addSeamlessToLinearGraph(state, graph, modelLoaderNodeId);
|
||||
@ -361,9 +348,6 @@ export const buildCanvasImageToImageGraph = (
|
||||
// optionally add custom VAE
|
||||
addVAEToGraph(state, graph, modelLoaderNodeId);
|
||||
|
||||
// add dynamic prompts - also sets up core iteration and seed
|
||||
addDynamicPromptsToGraph(state, graph);
|
||||
|
||||
// add controlnet, mutating `graph`
|
||||
addControlNetToLinearGraph(state, graph, DENOISE_LATENTS);
|
||||
|
||||
@ -381,5 +365,7 @@ export const buildCanvasImageToImageGraph = (
|
||||
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
|
||||
}
|
||||
|
||||
addSaveImageNode(state, graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -8,8 +8,6 @@ import {
|
||||
ImageToLatentsInvocation,
|
||||
MaskEdgeInvocation,
|
||||
NoiseInvocation,
|
||||
RandomIntInvocation,
|
||||
RangeOfSizeInvocation,
|
||||
} from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
@ -32,7 +30,6 @@ import {
|
||||
INPAINT_IMAGE,
|
||||
INPAINT_IMAGE_RESIZE_DOWN,
|
||||
INPAINT_IMAGE_RESIZE_UP,
|
||||
ITERATE,
|
||||
LATENTS_TO_IMAGE,
|
||||
MAIN_MODEL_LOADER,
|
||||
MASK_BLUR,
|
||||
@ -41,10 +38,9 @@ import {
|
||||
NEGATIVE_CONDITIONING,
|
||||
NOISE,
|
||||
POSITIVE_CONDITIONING,
|
||||
RANDOM_INT,
|
||||
RANGE_OF_SIZE,
|
||||
SEAMLESS,
|
||||
} from './constants';
|
||||
import { addSaveImageNode } from './addSaveImageNode';
|
||||
|
||||
/**
|
||||
* Builds the Canvas tab's Inpaint graph.
|
||||
@ -63,9 +59,7 @@ export const buildCanvasInpaintGraph = (
|
||||
scheduler,
|
||||
steps,
|
||||
img2imgStrength: strength,
|
||||
iterations,
|
||||
seed,
|
||||
shouldRandomizeSeed,
|
||||
vaePrecision,
|
||||
shouldUseNoiseSettings,
|
||||
shouldUseCpuNoise,
|
||||
@ -88,12 +82,8 @@ export const buildCanvasInpaintGraph = (
|
||||
const { width, height } = state.canvas.boundingBoxDimensions;
|
||||
|
||||
// We may need to set the inpaint width and height to scale the image
|
||||
const {
|
||||
scaledBoundingBoxDimensions,
|
||||
boundingBoxScaleMethod,
|
||||
shouldAutoSave,
|
||||
} = state.canvas;
|
||||
|
||||
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
|
||||
const is_intermediate = true;
|
||||
const fp32 = vaePrecision === 'fp32';
|
||||
|
||||
const isUsingScaledDimensions = ['auto', 'manual'].includes(
|
||||
@ -112,56 +102,57 @@ export const buildCanvasInpaintGraph = (
|
||||
[modelLoaderNodeId]: {
|
||||
type: 'main_model_loader',
|
||||
id: modelLoaderNodeId,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
model,
|
||||
},
|
||||
[CLIP_SKIP]: {
|
||||
type: 'clip_skip',
|
||||
id: CLIP_SKIP,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
skipped_layers: clipSkip,
|
||||
},
|
||||
[POSITIVE_CONDITIONING]: {
|
||||
type: 'compel',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
prompt: positivePrompt,
|
||||
},
|
||||
[NEGATIVE_CONDITIONING]: {
|
||||
type: 'compel',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
prompt: negativePrompt,
|
||||
},
|
||||
[MASK_BLUR]: {
|
||||
type: 'img_blur',
|
||||
id: MASK_BLUR,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
radius: maskBlur,
|
||||
blur_type: maskBlurMethod,
|
||||
},
|
||||
[INPAINT_IMAGE]: {
|
||||
type: 'i2l',
|
||||
id: INPAINT_IMAGE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
use_cpu,
|
||||
is_intermediate: true,
|
||||
seed,
|
||||
is_intermediate,
|
||||
},
|
||||
[INPAINT_CREATE_MASK]: {
|
||||
type: 'create_denoise_mask',
|
||||
id: INPAINT_CREATE_MASK,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
id: DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
steps: steps,
|
||||
cfg_scale: cfg_scale,
|
||||
scheduler: scheduler,
|
||||
@ -170,20 +161,21 @@ export const buildCanvasInpaintGraph = (
|
||||
},
|
||||
[CANVAS_COHERENCE_NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
id: CANVAS_COHERENCE_NOISE,
|
||||
use_cpu,
|
||||
is_intermediate: true,
|
||||
seed: seed + 1,
|
||||
is_intermediate,
|
||||
},
|
||||
[CANVAS_COHERENCE_NOISE_INCREMENT]: {
|
||||
type: 'add',
|
||||
id: CANVAS_COHERENCE_NOISE_INCREMENT,
|
||||
b: 1,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
},
|
||||
[CANVAS_COHERENCE_DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
id: CANVAS_COHERENCE_DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
steps: canvasCoherenceSteps,
|
||||
cfg_scale: cfg_scale,
|
||||
scheduler: scheduler,
|
||||
@ -193,29 +185,15 @@ export const buildCanvasInpaintGraph = (
|
||||
[LATENTS_TO_IMAGE]: {
|
||||
type: 'l2i',
|
||||
id: LATENTS_TO_IMAGE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[CANVAS_OUTPUT]: {
|
||||
type: 'color_correct',
|
||||
id: CANVAS_OUTPUT,
|
||||
is_intermediate: !shouldAutoSave,
|
||||
is_intermediate,
|
||||
reference: canvasInitImage,
|
||||
},
|
||||
[RANGE_OF_SIZE]: {
|
||||
type: 'range_of_size',
|
||||
id: RANGE_OF_SIZE,
|
||||
is_intermediate: true,
|
||||
// seed - must be connected manually
|
||||
// start: 0,
|
||||
size: iterations,
|
||||
step: 1,
|
||||
},
|
||||
[ITERATE]: {
|
||||
type: 'iterate',
|
||||
id: ITERATE,
|
||||
is_intermediate: true,
|
||||
},
|
||||
},
|
||||
edges: [
|
||||
// Connect Model Loader to CLIP Skip and UNet
|
||||
@ -322,48 +300,7 @@ export const buildCanvasInpaintGraph = (
|
||||
field: 'denoise_mask',
|
||||
},
|
||||
},
|
||||
// Iterate
|
||||
{
|
||||
source: {
|
||||
node_id: RANGE_OF_SIZE,
|
||||
field: 'collection',
|
||||
},
|
||||
destination: {
|
||||
node_id: ITERATE,
|
||||
field: 'collection',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: NOISE,
|
||||
field: 'seed',
|
||||
},
|
||||
},
|
||||
// Canvas Refine
|
||||
{
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
|
||||
field: 'a',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
|
||||
field: 'value',
|
||||
},
|
||||
destination: {
|
||||
node_id: CANVAS_COHERENCE_NOISE,
|
||||
field: 'seed',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: modelLoaderNodeId,
|
||||
@ -437,7 +374,7 @@ export const buildCanvasInpaintGraph = (
|
||||
graph.nodes[INPAINT_IMAGE_RESIZE_UP] = {
|
||||
type: 'img_resize',
|
||||
id: INPAINT_IMAGE_RESIZE_UP,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
image: canvasInitImage,
|
||||
@ -445,7 +382,7 @@ export const buildCanvasInpaintGraph = (
|
||||
graph.nodes[MASK_RESIZE_UP] = {
|
||||
type: 'img_resize',
|
||||
id: MASK_RESIZE_UP,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
image: canvasMaskImage,
|
||||
@ -453,14 +390,14 @@ export const buildCanvasInpaintGraph = (
|
||||
graph.nodes[INPAINT_IMAGE_RESIZE_DOWN] = {
|
||||
type: 'img_resize',
|
||||
id: INPAINT_IMAGE_RESIZE_DOWN,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
graph.nodes[MASK_RESIZE_DOWN] = {
|
||||
type: 'img_resize',
|
||||
id: MASK_RESIZE_DOWN,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
@ -598,7 +535,7 @@ export const buildCanvasInpaintGraph = (
|
||||
graph.nodes[CANVAS_COHERENCE_INPAINT_CREATE_MASK] = {
|
||||
type: 'create_denoise_mask',
|
||||
id: CANVAS_COHERENCE_INPAINT_CREATE_MASK,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
};
|
||||
|
||||
@ -651,7 +588,7 @@ export const buildCanvasInpaintGraph = (
|
||||
graph.nodes[CANVAS_COHERENCE_MASK_EDGE] = {
|
||||
type: 'mask_edge',
|
||||
id: CANVAS_COHERENCE_MASK_EDGE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
edge_blur: maskBlur,
|
||||
edge_size: maskBlur * 2,
|
||||
low_threshold: 100,
|
||||
@ -702,26 +639,6 @@ export const buildCanvasInpaintGraph = (
|
||||
});
|
||||
}
|
||||
|
||||
// Handle Seed
|
||||
if (shouldRandomizeSeed) {
|
||||
// Random int node to generate the starting seed
|
||||
const randomIntNode: RandomIntInvocation = {
|
||||
id: RANDOM_INT,
|
||||
type: 'rand_int',
|
||||
};
|
||||
|
||||
graph.nodes[RANDOM_INT] = randomIntNode;
|
||||
|
||||
// Connect random int to the start of the range of size so the range starts on the random first seed
|
||||
graph.edges.push({
|
||||
source: { node_id: RANDOM_INT, field: 'value' },
|
||||
destination: { node_id: RANGE_OF_SIZE, field: 'start' },
|
||||
});
|
||||
} else {
|
||||
// User specified seed, so set the start of the range of size to the seed
|
||||
(graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed;
|
||||
}
|
||||
|
||||
// Add Seamless To Graph
|
||||
if (seamlessXAxis || seamlessYAxis) {
|
||||
addSeamlessToLinearGraph(state, graph, modelLoaderNodeId);
|
||||
@ -751,5 +668,7 @@ export const buildCanvasInpaintGraph = (
|
||||
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
|
||||
}
|
||||
|
||||
addSaveImageNode(state, graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -7,13 +7,12 @@ import {
|
||||
InfillPatchMatchInvocation,
|
||||
InfillTileInvocation,
|
||||
NoiseInvocation,
|
||||
RandomIntInvocation,
|
||||
RangeOfSizeInvocation,
|
||||
} from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
import { addLoRAsToGraph } from './addLoRAsToGraph';
|
||||
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
|
||||
import { addSaveImageNode } from './addSaveImageNode';
|
||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||
@ -33,7 +32,6 @@ import {
|
||||
INPAINT_IMAGE_RESIZE_UP,
|
||||
INPAINT_INFILL,
|
||||
INPAINT_INFILL_RESIZE_DOWN,
|
||||
ITERATE,
|
||||
LATENTS_TO_IMAGE,
|
||||
MAIN_MODEL_LOADER,
|
||||
MASK_COMBINE,
|
||||
@ -43,8 +41,6 @@ import {
|
||||
NEGATIVE_CONDITIONING,
|
||||
NOISE,
|
||||
POSITIVE_CONDITIONING,
|
||||
RANDOM_INT,
|
||||
RANGE_OF_SIZE,
|
||||
SEAMLESS,
|
||||
} from './constants';
|
||||
|
||||
@ -65,9 +61,7 @@ export const buildCanvasOutpaintGraph = (
|
||||
scheduler,
|
||||
steps,
|
||||
img2imgStrength: strength,
|
||||
iterations,
|
||||
seed,
|
||||
shouldRandomizeSeed,
|
||||
vaePrecision,
|
||||
shouldUseNoiseSettings,
|
||||
shouldUseCpuNoise,
|
||||
@ -92,14 +86,10 @@ export const buildCanvasOutpaintGraph = (
|
||||
const { width, height } = state.canvas.boundingBoxDimensions;
|
||||
|
||||
// We may need to set the inpaint width and height to scale the image
|
||||
const {
|
||||
scaledBoundingBoxDimensions,
|
||||
boundingBoxScaleMethod,
|
||||
shouldAutoSave,
|
||||
} = state.canvas;
|
||||
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
|
||||
|
||||
const fp32 = vaePrecision === 'fp32';
|
||||
|
||||
const is_intermediate = true;
|
||||
const isUsingScaledDimensions = ['auto', 'manual'].includes(
|
||||
boundingBoxScaleMethod
|
||||
);
|
||||
@ -116,61 +106,62 @@ export const buildCanvasOutpaintGraph = (
|
||||
[modelLoaderNodeId]: {
|
||||
type: 'main_model_loader',
|
||||
id: modelLoaderNodeId,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
model,
|
||||
},
|
||||
[CLIP_SKIP]: {
|
||||
type: 'clip_skip',
|
||||
id: CLIP_SKIP,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
skipped_layers: clipSkip,
|
||||
},
|
||||
[POSITIVE_CONDITIONING]: {
|
||||
type: 'compel',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
prompt: positivePrompt,
|
||||
},
|
||||
[NEGATIVE_CONDITIONING]: {
|
||||
type: 'compel',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
prompt: negativePrompt,
|
||||
},
|
||||
[MASK_FROM_ALPHA]: {
|
||||
type: 'tomask',
|
||||
id: MASK_FROM_ALPHA,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
image: canvasInitImage,
|
||||
},
|
||||
[MASK_COMBINE]: {
|
||||
type: 'mask_combine',
|
||||
id: MASK_COMBINE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
mask2: canvasMaskImage,
|
||||
},
|
||||
[INPAINT_IMAGE]: {
|
||||
type: 'i2l',
|
||||
id: INPAINT_IMAGE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
use_cpu,
|
||||
is_intermediate: true,
|
||||
seed,
|
||||
is_intermediate,
|
||||
},
|
||||
[INPAINT_CREATE_MASK]: {
|
||||
type: 'create_denoise_mask',
|
||||
id: INPAINT_CREATE_MASK,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
id: DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
steps: steps,
|
||||
cfg_scale: cfg_scale,
|
||||
scheduler: scheduler,
|
||||
@ -179,20 +170,21 @@ export const buildCanvasOutpaintGraph = (
|
||||
},
|
||||
[CANVAS_COHERENCE_NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
id: CANVAS_COHERENCE_NOISE,
|
||||
use_cpu,
|
||||
is_intermediate: true,
|
||||
seed: seed + 1,
|
||||
is_intermediate,
|
||||
},
|
||||
[CANVAS_COHERENCE_NOISE_INCREMENT]: {
|
||||
type: 'add',
|
||||
id: CANVAS_COHERENCE_NOISE_INCREMENT,
|
||||
b: 1,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
},
|
||||
[CANVAS_COHERENCE_DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
id: CANVAS_COHERENCE_DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
steps: canvasCoherenceSteps,
|
||||
cfg_scale: cfg_scale,
|
||||
scheduler: scheduler,
|
||||
@ -202,27 +194,13 @@ export const buildCanvasOutpaintGraph = (
|
||||
[LATENTS_TO_IMAGE]: {
|
||||
type: 'l2i',
|
||||
id: LATENTS_TO_IMAGE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[CANVAS_OUTPUT]: {
|
||||
type: 'color_correct',
|
||||
id: CANVAS_OUTPUT,
|
||||
is_intermediate: !shouldAutoSave,
|
||||
},
|
||||
[RANGE_OF_SIZE]: {
|
||||
type: 'range_of_size',
|
||||
id: RANGE_OF_SIZE,
|
||||
is_intermediate: true,
|
||||
// seed - must be connected manually
|
||||
// start: 0,
|
||||
size: iterations,
|
||||
step: 1,
|
||||
},
|
||||
[ITERATE]: {
|
||||
type: 'iterate',
|
||||
id: ITERATE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
},
|
||||
},
|
||||
edges: [
|
||||
@ -352,48 +330,7 @@ export const buildCanvasOutpaintGraph = (
|
||||
field: 'denoise_mask',
|
||||
},
|
||||
},
|
||||
// Iterate
|
||||
{
|
||||
source: {
|
||||
node_id: RANGE_OF_SIZE,
|
||||
field: 'collection',
|
||||
},
|
||||
destination: {
|
||||
node_id: ITERATE,
|
||||
field: 'collection',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: NOISE,
|
||||
field: 'seed',
|
||||
},
|
||||
},
|
||||
// Canvas Refine
|
||||
{
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
|
||||
field: 'a',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
|
||||
field: 'value',
|
||||
},
|
||||
destination: {
|
||||
node_id: CANVAS_COHERENCE_NOISE,
|
||||
field: 'seed',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: modelLoaderNodeId,
|
||||
@ -473,7 +410,7 @@ export const buildCanvasOutpaintGraph = (
|
||||
graph.nodes[INPAINT_INFILL] = {
|
||||
type: 'infill_patchmatch',
|
||||
id: INPAINT_INFILL,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
downscale: infillPatchmatchDownscaleSize,
|
||||
};
|
||||
}
|
||||
@ -482,7 +419,7 @@ export const buildCanvasOutpaintGraph = (
|
||||
graph.nodes[INPAINT_INFILL] = {
|
||||
type: 'infill_lama',
|
||||
id: INPAINT_INFILL,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
};
|
||||
}
|
||||
|
||||
@ -490,7 +427,7 @@ export const buildCanvasOutpaintGraph = (
|
||||
graph.nodes[INPAINT_INFILL] = {
|
||||
type: 'infill_cv2',
|
||||
id: INPAINT_INFILL,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
};
|
||||
}
|
||||
|
||||
@ -498,7 +435,7 @@ export const buildCanvasOutpaintGraph = (
|
||||
graph.nodes[INPAINT_INFILL] = {
|
||||
type: 'infill_tile',
|
||||
id: INPAINT_INFILL,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
tile_size: infillTileSize,
|
||||
};
|
||||
}
|
||||
@ -512,7 +449,7 @@ export const buildCanvasOutpaintGraph = (
|
||||
graph.nodes[INPAINT_IMAGE_RESIZE_UP] = {
|
||||
type: 'img_resize',
|
||||
id: INPAINT_IMAGE_RESIZE_UP,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
image: canvasInitImage,
|
||||
@ -520,28 +457,28 @@ export const buildCanvasOutpaintGraph = (
|
||||
graph.nodes[MASK_RESIZE_UP] = {
|
||||
type: 'img_resize',
|
||||
id: MASK_RESIZE_UP,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
};
|
||||
graph.nodes[INPAINT_IMAGE_RESIZE_DOWN] = {
|
||||
type: 'img_resize',
|
||||
id: INPAINT_IMAGE_RESIZE_DOWN,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
graph.nodes[INPAINT_INFILL_RESIZE_DOWN] = {
|
||||
type: 'img_resize',
|
||||
id: INPAINT_INFILL_RESIZE_DOWN,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
graph.nodes[MASK_RESIZE_DOWN] = {
|
||||
type: 'img_resize',
|
||||
id: MASK_RESIZE_DOWN,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
@ -700,7 +637,7 @@ export const buildCanvasOutpaintGraph = (
|
||||
graph.nodes[CANVAS_COHERENCE_INPAINT_CREATE_MASK] = {
|
||||
type: 'create_denoise_mask',
|
||||
id: CANVAS_COHERENCE_INPAINT_CREATE_MASK,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
};
|
||||
|
||||
@ -747,7 +684,7 @@ export const buildCanvasOutpaintGraph = (
|
||||
graph.nodes[CANVAS_COHERENCE_MASK_EDGE] = {
|
||||
type: 'mask_edge',
|
||||
id: CANVAS_COHERENCE_MASK_EDGE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
edge_blur: maskBlur,
|
||||
edge_size: maskBlur * 2,
|
||||
low_threshold: 100,
|
||||
@ -804,26 +741,6 @@ export const buildCanvasOutpaintGraph = (
|
||||
});
|
||||
}
|
||||
|
||||
// Handle Seed
|
||||
if (shouldRandomizeSeed) {
|
||||
// Random int node to generate the starting seed
|
||||
const randomIntNode: RandomIntInvocation = {
|
||||
id: RANDOM_INT,
|
||||
type: 'rand_int',
|
||||
};
|
||||
|
||||
graph.nodes[RANDOM_INT] = randomIntNode;
|
||||
|
||||
// Connect random int to the start of the range of size so the range starts on the random first seed
|
||||
graph.edges.push({
|
||||
source: { node_id: RANDOM_INT, field: 'value' },
|
||||
destination: { node_id: RANGE_OF_SIZE, field: 'start' },
|
||||
});
|
||||
} else {
|
||||
// User specified seed, so set the start of the range of size to the seed
|
||||
(graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed;
|
||||
}
|
||||
|
||||
// Add Seamless To Graph
|
||||
if (seamlessXAxis || seamlessYAxis) {
|
||||
addSeamlessToLinearGraph(state, graph, modelLoaderNodeId);
|
||||
@ -853,5 +770,7 @@ export const buildCanvasOutpaintGraph = (
|
||||
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
|
||||
}
|
||||
|
||||
addSaveImageNode(state, graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -4,11 +4,11 @@ import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { initialGenerationState } from 'features/parameters/store/generationSlice';
|
||||
import { ImageDTO, ImageToLatentsInvocation } from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
|
||||
import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph';
|
||||
import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph';
|
||||
import { addSaveImageNode } from './addSaveImageNode';
|
||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||
@ -27,7 +27,7 @@ import {
|
||||
SDXL_REFINER_SEAMLESS,
|
||||
SEAMLESS,
|
||||
} from './constants';
|
||||
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
|
||||
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
||||
|
||||
/**
|
||||
* Builds the Canvas tab's Image to Image graph.
|
||||
@ -43,6 +43,7 @@ export const buildCanvasSDXLImageToImageGraph = (
|
||||
model,
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
seed,
|
||||
steps,
|
||||
vaePrecision,
|
||||
clipSkip,
|
||||
@ -56,20 +57,15 @@ export const buildCanvasSDXLImageToImageGraph = (
|
||||
shouldUseSDXLRefiner,
|
||||
refinerStart,
|
||||
sdxlImg2ImgDenoisingStrength: strength,
|
||||
shouldConcatSDXLStylePrompt,
|
||||
} = state.sdxl;
|
||||
|
||||
// The bounding box determines width and height, not the width and height params
|
||||
const { width, height } = state.canvas.boundingBoxDimensions;
|
||||
|
||||
const {
|
||||
scaledBoundingBoxDimensions,
|
||||
boundingBoxScaleMethod,
|
||||
shouldAutoSave,
|
||||
} = state.canvas;
|
||||
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
|
||||
|
||||
const fp32 = vaePrecision === 'fp32';
|
||||
|
||||
const is_intermediate = true;
|
||||
const isUsingScaledDimensions = ['auto', 'manual'].includes(
|
||||
boundingBoxScaleMethod
|
||||
);
|
||||
@ -87,8 +83,8 @@ export const buildCanvasSDXLImageToImageGraph = (
|
||||
: initialGenerationState.shouldUseCpuNoise;
|
||||
|
||||
// Construct Style Prompt
|
||||
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
|
||||
craftSDXLStylePrompt(state, shouldConcatSDXLStylePrompt);
|
||||
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
|
||||
buildSDXLStylePrompts(state);
|
||||
|
||||
/**
|
||||
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the
|
||||
@ -112,19 +108,20 @@ export const buildCanvasSDXLImageToImageGraph = (
|
||||
type: 'sdxl_compel_prompt',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
prompt: positivePrompt,
|
||||
style: craftedPositiveStylePrompt,
|
||||
style: joinedPositiveStylePrompt,
|
||||
},
|
||||
[NEGATIVE_CONDITIONING]: {
|
||||
type: 'sdxl_compel_prompt',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
prompt: negativePrompt,
|
||||
style: craftedNegativeStylePrompt,
|
||||
style: joinedNegativeStylePrompt,
|
||||
},
|
||||
[NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
use_cpu,
|
||||
seed,
|
||||
width: !isUsingScaledDimensions
|
||||
? width
|
||||
: scaledBoundingBoxDimensions.width,
|
||||
@ -135,13 +132,13 @@ export const buildCanvasSDXLImageToImageGraph = (
|
||||
[IMAGE_TO_LATENTS]: {
|
||||
type: 'i2l',
|
||||
id: IMAGE_TO_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[SDXL_DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
id: SDXL_DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
@ -252,7 +249,7 @@ export const buildCanvasSDXLImageToImageGraph = (
|
||||
graph.nodes[IMG2IMG_RESIZE] = {
|
||||
id: IMG2IMG_RESIZE,
|
||||
type: 'img_resize',
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
image: initialImage,
|
||||
width: scaledBoundingBoxDimensions.width,
|
||||
height: scaledBoundingBoxDimensions.height,
|
||||
@ -260,13 +257,13 @@ export const buildCanvasSDXLImageToImageGraph = (
|
||||
graph.nodes[LATENTS_TO_IMAGE] = {
|
||||
id: LATENTS_TO_IMAGE,
|
||||
type: 'l2i',
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
};
|
||||
graph.nodes[CANVAS_OUTPUT] = {
|
||||
id: CANVAS_OUTPUT,
|
||||
type: 'img_resize',
|
||||
is_intermediate: !shouldAutoSave,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
@ -307,7 +304,7 @@ export const buildCanvasSDXLImageToImageGraph = (
|
||||
graph.nodes[CANVAS_OUTPUT] = {
|
||||
type: 'l2i',
|
||||
id: CANVAS_OUTPUT,
|
||||
is_intermediate: !shouldAutoSave,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
};
|
||||
|
||||
@ -336,10 +333,10 @@ export const buildCanvasSDXLImageToImageGraph = (
|
||||
height: !isUsingScaledDimensions
|
||||
? height
|
||||
: scaledBoundingBoxDimensions.height,
|
||||
positive_prompt: '', // set in addDynamicPromptsToGraph
|
||||
positive_prompt: positivePrompt,
|
||||
negative_prompt: negativePrompt,
|
||||
model,
|
||||
seed: 0, // set in addDynamicPromptsToGraph
|
||||
seed,
|
||||
steps,
|
||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||
scheduler,
|
||||
@ -387,9 +384,6 @@ export const buildCanvasSDXLImageToImageGraph = (
|
||||
// add LoRA support
|
||||
addSDXLLoRAsToGraph(state, graph, SDXL_DENOISE_LATENTS, modelLoaderNodeId);
|
||||
|
||||
// add dynamic prompts - also sets up core iteration and seed
|
||||
addDynamicPromptsToGraph(state, graph);
|
||||
|
||||
// add controlnet, mutating `graph`
|
||||
addControlNetToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
|
||||
|
||||
@ -407,5 +401,7 @@ export const buildCanvasSDXLImageToImageGraph = (
|
||||
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
|
||||
}
|
||||
|
||||
addSaveImageNode(state, graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -8,13 +8,13 @@ import {
|
||||
ImageToLatentsInvocation,
|
||||
MaskEdgeInvocation,
|
||||
NoiseInvocation,
|
||||
RandomIntInvocation,
|
||||
RangeOfSizeInvocation,
|
||||
} from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
|
||||
import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph';
|
||||
import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph';
|
||||
import { addSaveImageNode } from './addSaveImageNode';
|
||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||
@ -29,7 +29,6 @@ import {
|
||||
INPAINT_IMAGE,
|
||||
INPAINT_IMAGE_RESIZE_DOWN,
|
||||
INPAINT_IMAGE_RESIZE_UP,
|
||||
ITERATE,
|
||||
LATENTS_TO_IMAGE,
|
||||
MASK_BLUR,
|
||||
MASK_RESIZE_DOWN,
|
||||
@ -37,16 +36,13 @@ import {
|
||||
NEGATIVE_CONDITIONING,
|
||||
NOISE,
|
||||
POSITIVE_CONDITIONING,
|
||||
RANDOM_INT,
|
||||
RANGE_OF_SIZE,
|
||||
SDXL_CANVAS_INPAINT_GRAPH,
|
||||
SDXL_DENOISE_LATENTS,
|
||||
SDXL_MODEL_LOADER,
|
||||
SDXL_REFINER_SEAMLESS,
|
||||
SEAMLESS,
|
||||
} from './constants';
|
||||
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
||||
|
||||
/**
|
||||
* Builds the Canvas tab's Inpaint graph.
|
||||
@ -64,9 +60,7 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
iterations,
|
||||
seed,
|
||||
shouldRandomizeSeed,
|
||||
vaePrecision,
|
||||
shouldUseNoiseSettings,
|
||||
shouldUseCpuNoise,
|
||||
@ -83,7 +77,6 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
sdxlImg2ImgDenoisingStrength: strength,
|
||||
shouldUseSDXLRefiner,
|
||||
refinerStart,
|
||||
shouldConcatSDXLStylePrompt,
|
||||
} = state.sdxl;
|
||||
|
||||
if (!model) {
|
||||
@ -95,14 +88,10 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
const { width, height } = state.canvas.boundingBoxDimensions;
|
||||
|
||||
// We may need to set the inpaint width and height to scale the image
|
||||
const {
|
||||
scaledBoundingBoxDimensions,
|
||||
boundingBoxScaleMethod,
|
||||
shouldAutoSave,
|
||||
} = state.canvas;
|
||||
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
|
||||
|
||||
const fp32 = vaePrecision === 'fp32';
|
||||
|
||||
const is_intermediate = true;
|
||||
const isUsingScaledDimensions = ['auto', 'manual'].includes(
|
||||
boundingBoxScaleMethod
|
||||
);
|
||||
@ -114,8 +103,8 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
: shouldUseCpuNoise;
|
||||
|
||||
// Construct Style Prompt
|
||||
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
|
||||
craftSDXLStylePrompt(state, shouldConcatSDXLStylePrompt);
|
||||
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
|
||||
buildSDXLStylePrompts(state);
|
||||
|
||||
const graph: NonNullableGraph = {
|
||||
id: SDXL_CANVAS_INPAINT_GRAPH,
|
||||
@ -129,43 +118,44 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
type: 'sdxl_compel_prompt',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
prompt: positivePrompt,
|
||||
style: craftedPositiveStylePrompt,
|
||||
style: joinedPositiveStylePrompt,
|
||||
},
|
||||
[NEGATIVE_CONDITIONING]: {
|
||||
type: 'sdxl_compel_prompt',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
prompt: negativePrompt,
|
||||
style: craftedNegativeStylePrompt,
|
||||
style: joinedNegativeStylePrompt,
|
||||
},
|
||||
[MASK_BLUR]: {
|
||||
type: 'img_blur',
|
||||
id: MASK_BLUR,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
radius: maskBlur,
|
||||
blur_type: maskBlurMethod,
|
||||
},
|
||||
[INPAINT_IMAGE]: {
|
||||
type: 'i2l',
|
||||
id: INPAINT_IMAGE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
use_cpu,
|
||||
is_intermediate: true,
|
||||
seed,
|
||||
is_intermediate,
|
||||
},
|
||||
[INPAINT_CREATE_MASK]: {
|
||||
type: 'create_denoise_mask',
|
||||
id: INPAINT_CREATE_MASK,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[SDXL_DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
id: SDXL_DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
steps: steps,
|
||||
cfg_scale: cfg_scale,
|
||||
scheduler: scheduler,
|
||||
@ -176,20 +166,21 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
},
|
||||
[CANVAS_COHERENCE_NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
id: CANVAS_COHERENCE_NOISE,
|
||||
use_cpu,
|
||||
is_intermediate: true,
|
||||
seed: seed + 1,
|
||||
is_intermediate,
|
||||
},
|
||||
[CANVAS_COHERENCE_NOISE_INCREMENT]: {
|
||||
type: 'add',
|
||||
id: CANVAS_COHERENCE_NOISE_INCREMENT,
|
||||
b: 1,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
},
|
||||
[CANVAS_COHERENCE_DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
id: CANVAS_COHERENCE_DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
steps: canvasCoherenceSteps,
|
||||
cfg_scale: cfg_scale,
|
||||
scheduler: scheduler,
|
||||
@ -199,29 +190,15 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
[LATENTS_TO_IMAGE]: {
|
||||
type: 'l2i',
|
||||
id: LATENTS_TO_IMAGE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[CANVAS_OUTPUT]: {
|
||||
type: 'color_correct',
|
||||
id: CANVAS_OUTPUT,
|
||||
is_intermediate: !shouldAutoSave,
|
||||
is_intermediate,
|
||||
reference: canvasInitImage,
|
||||
},
|
||||
[RANGE_OF_SIZE]: {
|
||||
type: 'range_of_size',
|
||||
id: RANGE_OF_SIZE,
|
||||
is_intermediate: true,
|
||||
// seed - must be connected manually
|
||||
// start: 0,
|
||||
size: iterations,
|
||||
step: 1,
|
||||
},
|
||||
[ITERATE]: {
|
||||
type: 'iterate',
|
||||
id: ITERATE,
|
||||
is_intermediate: true,
|
||||
},
|
||||
},
|
||||
edges: [
|
||||
// Connect Model Loader to UNet and CLIP
|
||||
@ -337,48 +314,7 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
field: 'denoise_mask',
|
||||
},
|
||||
},
|
||||
// Iterate
|
||||
{
|
||||
source: {
|
||||
node_id: RANGE_OF_SIZE,
|
||||
field: 'collection',
|
||||
},
|
||||
destination: {
|
||||
node_id: ITERATE,
|
||||
field: 'collection',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: NOISE,
|
||||
field: 'seed',
|
||||
},
|
||||
},
|
||||
// Canvas Refine
|
||||
{
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
|
||||
field: 'a',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
|
||||
field: 'value',
|
||||
},
|
||||
destination: {
|
||||
node_id: CANVAS_COHERENCE_NOISE,
|
||||
field: 'seed',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: modelLoaderNodeId,
|
||||
@ -452,7 +388,7 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
graph.nodes[INPAINT_IMAGE_RESIZE_UP] = {
|
||||
type: 'img_resize',
|
||||
id: INPAINT_IMAGE_RESIZE_UP,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
image: canvasInitImage,
|
||||
@ -460,7 +396,7 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
graph.nodes[MASK_RESIZE_UP] = {
|
||||
type: 'img_resize',
|
||||
id: MASK_RESIZE_UP,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
image: canvasMaskImage,
|
||||
@ -468,14 +404,14 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
graph.nodes[INPAINT_IMAGE_RESIZE_DOWN] = {
|
||||
type: 'img_resize',
|
||||
id: INPAINT_IMAGE_RESIZE_DOWN,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
graph.nodes[MASK_RESIZE_DOWN] = {
|
||||
type: 'img_resize',
|
||||
id: MASK_RESIZE_DOWN,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
@ -613,7 +549,7 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
graph.nodes[CANVAS_COHERENCE_INPAINT_CREATE_MASK] = {
|
||||
type: 'create_denoise_mask',
|
||||
id: CANVAS_COHERENCE_INPAINT_CREATE_MASK,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
};
|
||||
|
||||
@ -666,7 +602,7 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
graph.nodes[CANVAS_COHERENCE_MASK_EDGE] = {
|
||||
type: 'mask_edge',
|
||||
id: CANVAS_COHERENCE_MASK_EDGE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
edge_blur: maskBlur,
|
||||
edge_size: maskBlur * 2,
|
||||
low_threshold: 100,
|
||||
@ -717,26 +653,6 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
});
|
||||
}
|
||||
|
||||
// Handle Seed
|
||||
if (shouldRandomizeSeed) {
|
||||
// Random int node to generate the starting seed
|
||||
const randomIntNode: RandomIntInvocation = {
|
||||
id: RANDOM_INT,
|
||||
type: 'rand_int',
|
||||
};
|
||||
|
||||
graph.nodes[RANDOM_INT] = randomIntNode;
|
||||
|
||||
// Connect random int to the start of the range of size so the range starts on the random first seed
|
||||
graph.edges.push({
|
||||
source: { node_id: RANDOM_INT, field: 'value' },
|
||||
destination: { node_id: RANGE_OF_SIZE, field: 'start' },
|
||||
});
|
||||
} else {
|
||||
// User specified seed, so set the start of the range of size to the seed
|
||||
(graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed;
|
||||
}
|
||||
|
||||
// Add Seamless To Graph
|
||||
if (seamlessXAxis || seamlessYAxis) {
|
||||
addSeamlessToLinearGraph(state, graph, modelLoaderNodeId);
|
||||
@ -780,5 +696,7 @@ export const buildCanvasSDXLInpaintGraph = (
|
||||
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
|
||||
}
|
||||
|
||||
addSaveImageNode(state, graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -7,14 +7,13 @@ import {
|
||||
InfillPatchMatchInvocation,
|
||||
InfillTileInvocation,
|
||||
NoiseInvocation,
|
||||
RandomIntInvocation,
|
||||
RangeOfSizeInvocation,
|
||||
} from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
|
||||
import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph';
|
||||
import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph';
|
||||
import { addSaveImageNode } from './addSaveImageNode';
|
||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||
@ -31,7 +30,6 @@ import {
|
||||
INPAINT_IMAGE_RESIZE_UP,
|
||||
INPAINT_INFILL,
|
||||
INPAINT_INFILL_RESIZE_DOWN,
|
||||
ITERATE,
|
||||
LATENTS_TO_IMAGE,
|
||||
MASK_COMBINE,
|
||||
MASK_FROM_ALPHA,
|
||||
@ -40,15 +38,13 @@ import {
|
||||
NEGATIVE_CONDITIONING,
|
||||
NOISE,
|
||||
POSITIVE_CONDITIONING,
|
||||
RANDOM_INT,
|
||||
RANGE_OF_SIZE,
|
||||
SDXL_CANVAS_OUTPAINT_GRAPH,
|
||||
SDXL_DENOISE_LATENTS,
|
||||
SDXL_MODEL_LOADER,
|
||||
SDXL_REFINER_SEAMLESS,
|
||||
SEAMLESS,
|
||||
} from './constants';
|
||||
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
|
||||
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
||||
|
||||
/**
|
||||
* Builds the Canvas tab's Outpaint graph.
|
||||
@ -66,9 +62,7 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
iterations,
|
||||
seed,
|
||||
shouldRandomizeSeed,
|
||||
vaePrecision,
|
||||
shouldUseNoiseSettings,
|
||||
shouldUseCpuNoise,
|
||||
@ -87,7 +81,6 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
sdxlImg2ImgDenoisingStrength: strength,
|
||||
shouldUseSDXLRefiner,
|
||||
refinerStart,
|
||||
shouldConcatSDXLStylePrompt,
|
||||
} = state.sdxl;
|
||||
|
||||
if (!model) {
|
||||
@ -99,14 +92,10 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
const { width, height } = state.canvas.boundingBoxDimensions;
|
||||
|
||||
// We may need to set the inpaint width and height to scale the image
|
||||
const {
|
||||
scaledBoundingBoxDimensions,
|
||||
boundingBoxScaleMethod,
|
||||
shouldAutoSave,
|
||||
} = state.canvas;
|
||||
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
|
||||
|
||||
const fp32 = vaePrecision === 'fp32';
|
||||
|
||||
const is_intermediate = true;
|
||||
const isUsingScaledDimensions = ['auto', 'manual'].includes(
|
||||
boundingBoxScaleMethod
|
||||
);
|
||||
@ -118,8 +107,8 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
: shouldUseCpuNoise;
|
||||
|
||||
// Construct Style Prompt
|
||||
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
|
||||
craftSDXLStylePrompt(state, shouldConcatSDXLStylePrompt);
|
||||
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
|
||||
buildSDXLStylePrompts(state);
|
||||
|
||||
const graph: NonNullableGraph = {
|
||||
id: SDXL_CANVAS_OUTPAINT_GRAPH,
|
||||
@ -133,48 +122,49 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
type: 'sdxl_compel_prompt',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
prompt: positivePrompt,
|
||||
style: craftedPositiveStylePrompt,
|
||||
style: joinedPositiveStylePrompt,
|
||||
},
|
||||
[NEGATIVE_CONDITIONING]: {
|
||||
type: 'sdxl_compel_prompt',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
prompt: negativePrompt,
|
||||
style: craftedNegativeStylePrompt,
|
||||
style: joinedNegativeStylePrompt,
|
||||
},
|
||||
[MASK_FROM_ALPHA]: {
|
||||
type: 'tomask',
|
||||
id: MASK_FROM_ALPHA,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
image: canvasInitImage,
|
||||
},
|
||||
[MASK_COMBINE]: {
|
||||
type: 'mask_combine',
|
||||
id: MASK_COMBINE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
mask2: canvasMaskImage,
|
||||
},
|
||||
[INPAINT_IMAGE]: {
|
||||
type: 'i2l',
|
||||
id: INPAINT_IMAGE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
use_cpu,
|
||||
is_intermediate: true,
|
||||
seed,
|
||||
is_intermediate,
|
||||
},
|
||||
[INPAINT_CREATE_MASK]: {
|
||||
type: 'create_denoise_mask',
|
||||
id: INPAINT_CREATE_MASK,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[SDXL_DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
id: SDXL_DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
steps: steps,
|
||||
cfg_scale: cfg_scale,
|
||||
scheduler: scheduler,
|
||||
@ -185,20 +175,21 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
},
|
||||
[CANVAS_COHERENCE_NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
id: CANVAS_COHERENCE_NOISE,
|
||||
use_cpu,
|
||||
is_intermediate: true,
|
||||
seed: seed + 1,
|
||||
is_intermediate,
|
||||
},
|
||||
[CANVAS_COHERENCE_NOISE_INCREMENT]: {
|
||||
type: 'add',
|
||||
id: CANVAS_COHERENCE_NOISE_INCREMENT,
|
||||
b: 1,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
},
|
||||
[CANVAS_COHERENCE_DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
id: CANVAS_COHERENCE_DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
steps: canvasCoherenceSteps,
|
||||
cfg_scale: cfg_scale,
|
||||
scheduler: scheduler,
|
||||
@ -208,27 +199,13 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
[LATENTS_TO_IMAGE]: {
|
||||
type: 'l2i',
|
||||
id: LATENTS_TO_IMAGE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
},
|
||||
[CANVAS_OUTPUT]: {
|
||||
type: 'color_correct',
|
||||
id: CANVAS_OUTPUT,
|
||||
is_intermediate: !shouldAutoSave,
|
||||
},
|
||||
[RANGE_OF_SIZE]: {
|
||||
type: 'range_of_size',
|
||||
id: RANGE_OF_SIZE,
|
||||
is_intermediate: true,
|
||||
// seed - must be connected manually
|
||||
// start: 0,
|
||||
size: iterations,
|
||||
step: 1,
|
||||
},
|
||||
[ITERATE]: {
|
||||
type: 'iterate',
|
||||
id: ITERATE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
},
|
||||
},
|
||||
edges: [
|
||||
@ -367,48 +344,7 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
field: 'denoise_mask',
|
||||
},
|
||||
},
|
||||
// Iterate
|
||||
{
|
||||
source: {
|
||||
node_id: RANGE_OF_SIZE,
|
||||
field: 'collection',
|
||||
},
|
||||
destination: {
|
||||
node_id: ITERATE,
|
||||
field: 'collection',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: NOISE,
|
||||
field: 'seed',
|
||||
},
|
||||
},
|
||||
// Canvas Refine
|
||||
{
|
||||
source: {
|
||||
node_id: ITERATE,
|
||||
field: 'item',
|
||||
},
|
||||
destination: {
|
||||
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
|
||||
field: 'a',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: CANVAS_COHERENCE_NOISE_INCREMENT,
|
||||
field: 'value',
|
||||
},
|
||||
destination: {
|
||||
node_id: CANVAS_COHERENCE_NOISE,
|
||||
field: 'seed',
|
||||
},
|
||||
},
|
||||
{
|
||||
source: {
|
||||
node_id: modelLoaderNodeId,
|
||||
@ -488,7 +424,7 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
graph.nodes[INPAINT_INFILL] = {
|
||||
type: 'infill_patchmatch',
|
||||
id: INPAINT_INFILL,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
downscale: infillPatchmatchDownscaleSize,
|
||||
};
|
||||
}
|
||||
@ -497,7 +433,7 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
graph.nodes[INPAINT_INFILL] = {
|
||||
type: 'infill_lama',
|
||||
id: INPAINT_INFILL,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
};
|
||||
}
|
||||
|
||||
@ -505,7 +441,7 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
graph.nodes[INPAINT_INFILL] = {
|
||||
type: 'infill_cv2',
|
||||
id: INPAINT_INFILL,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
};
|
||||
}
|
||||
|
||||
@ -513,7 +449,7 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
graph.nodes[INPAINT_INFILL] = {
|
||||
type: 'infill_tile',
|
||||
id: INPAINT_INFILL,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
tile_size: infillTileSize,
|
||||
};
|
||||
}
|
||||
@ -527,7 +463,7 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
graph.nodes[INPAINT_IMAGE_RESIZE_UP] = {
|
||||
type: 'img_resize',
|
||||
id: INPAINT_IMAGE_RESIZE_UP,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
image: canvasInitImage,
|
||||
@ -535,28 +471,28 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
graph.nodes[MASK_RESIZE_UP] = {
|
||||
type: 'img_resize',
|
||||
id: MASK_RESIZE_UP,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
};
|
||||
graph.nodes[INPAINT_IMAGE_RESIZE_DOWN] = {
|
||||
type: 'img_resize',
|
||||
id: INPAINT_IMAGE_RESIZE_DOWN,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
graph.nodes[INPAINT_INFILL_RESIZE_DOWN] = {
|
||||
type: 'img_resize',
|
||||
id: INPAINT_INFILL_RESIZE_DOWN,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
graph.nodes[MASK_RESIZE_DOWN] = {
|
||||
type: 'img_resize',
|
||||
id: MASK_RESIZE_DOWN,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
@ -716,7 +652,7 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
graph.nodes[CANVAS_COHERENCE_INPAINT_CREATE_MASK] = {
|
||||
type: 'create_denoise_mask',
|
||||
id: CANVAS_COHERENCE_INPAINT_CREATE_MASK,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
};
|
||||
|
||||
@ -763,7 +699,7 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
graph.nodes[CANVAS_COHERENCE_MASK_EDGE] = {
|
||||
type: 'mask_edge',
|
||||
id: CANVAS_COHERENCE_MASK_EDGE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
edge_blur: maskBlur,
|
||||
edge_size: maskBlur * 2,
|
||||
low_threshold: 100,
|
||||
@ -820,26 +756,6 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
});
|
||||
}
|
||||
|
||||
// Handle Seed
|
||||
if (shouldRandomizeSeed) {
|
||||
// Random int node to generate the starting seed
|
||||
const randomIntNode: RandomIntInvocation = {
|
||||
id: RANDOM_INT,
|
||||
type: 'rand_int',
|
||||
};
|
||||
|
||||
graph.nodes[RANDOM_INT] = randomIntNode;
|
||||
|
||||
// Connect random int to the start of the range of size so the range starts on the random first seed
|
||||
graph.edges.push({
|
||||
source: { node_id: RANDOM_INT, field: 'value' },
|
||||
destination: { node_id: RANGE_OF_SIZE, field: 'start' },
|
||||
});
|
||||
} else {
|
||||
// User specified seed, so set the start of the range of size to the seed
|
||||
(graph.nodes[RANGE_OF_SIZE] as RangeOfSizeInvocation).start = seed;
|
||||
}
|
||||
|
||||
// Add Seamless To Graph
|
||||
if (seamlessXAxis || seamlessYAxis) {
|
||||
addSeamlessToLinearGraph(state, graph, modelLoaderNodeId);
|
||||
@ -883,5 +799,7 @@ export const buildCanvasSDXLOutpaintGraph = (
|
||||
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
|
||||
}
|
||||
|
||||
addSaveImageNode(state, graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -7,11 +7,11 @@ import {
|
||||
ONNXTextToLatentsInvocation,
|
||||
} from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
|
||||
import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph';
|
||||
import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph';
|
||||
import { addSaveImageNode } from './addSaveImageNode';
|
||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||
@ -29,7 +29,7 @@ import {
|
||||
SDXL_REFINER_SEAMLESS,
|
||||
SEAMLESS,
|
||||
} from './constants';
|
||||
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
|
||||
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
||||
|
||||
/**
|
||||
* Builds the Canvas tab's Text to Image graph.
|
||||
@ -44,6 +44,7 @@ export const buildCanvasSDXLTextToImageGraph = (
|
||||
model,
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
seed,
|
||||
steps,
|
||||
vaePrecision,
|
||||
clipSkip,
|
||||
@ -56,20 +57,15 @@ export const buildCanvasSDXLTextToImageGraph = (
|
||||
// The bounding box determines width and height, not the width and height params
|
||||
const { width, height } = state.canvas.boundingBoxDimensions;
|
||||
|
||||
const {
|
||||
scaledBoundingBoxDimensions,
|
||||
boundingBoxScaleMethod,
|
||||
shouldAutoSave,
|
||||
} = state.canvas;
|
||||
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
|
||||
|
||||
const fp32 = vaePrecision === 'fp32';
|
||||
|
||||
const is_intermediate = true;
|
||||
const isUsingScaledDimensions = ['auto', 'manual'].includes(
|
||||
boundingBoxScaleMethod
|
||||
);
|
||||
|
||||
const { shouldUseSDXLRefiner, refinerStart, shouldConcatSDXLStylePrompt } =
|
||||
state.sdxl;
|
||||
const { shouldUseSDXLRefiner, refinerStart } = state.sdxl;
|
||||
|
||||
if (!model) {
|
||||
log.error('No model found in state');
|
||||
@ -95,7 +91,7 @@ export const buildCanvasSDXLTextToImageGraph = (
|
||||
? {
|
||||
type: 't2l_onnx',
|
||||
id: SDXL_DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
@ -103,7 +99,7 @@ export const buildCanvasSDXLTextToImageGraph = (
|
||||
: {
|
||||
type: 'denoise_latents',
|
||||
id: SDXL_DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
@ -112,8 +108,8 @@ export const buildCanvasSDXLTextToImageGraph = (
|
||||
};
|
||||
|
||||
// Construct Style Prompt
|
||||
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
|
||||
craftSDXLStylePrompt(state, shouldConcatSDXLStylePrompt);
|
||||
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
|
||||
buildSDXLStylePrompts(state);
|
||||
|
||||
/**
|
||||
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the
|
||||
@ -132,27 +128,28 @@ export const buildCanvasSDXLTextToImageGraph = (
|
||||
[modelLoaderNodeId]: {
|
||||
type: modelLoaderNodeType,
|
||||
id: modelLoaderNodeId,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
model,
|
||||
},
|
||||
[POSITIVE_CONDITIONING]: {
|
||||
type: isUsingOnnxModel ? 'prompt_onnx' : 'sdxl_compel_prompt',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
prompt: positivePrompt,
|
||||
style: craftedPositiveStylePrompt,
|
||||
style: joinedPositiveStylePrompt,
|
||||
},
|
||||
[NEGATIVE_CONDITIONING]: {
|
||||
type: isUsingOnnxModel ? 'prompt_onnx' : 'sdxl_compel_prompt',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
prompt: negativePrompt,
|
||||
style: craftedNegativeStylePrompt,
|
||||
style: joinedNegativeStylePrompt,
|
||||
},
|
||||
[NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
seed,
|
||||
width: !isUsingScaledDimensions
|
||||
? width
|
||||
: scaledBoundingBoxDimensions.width,
|
||||
@ -254,14 +251,14 @@ export const buildCanvasSDXLTextToImageGraph = (
|
||||
graph.nodes[LATENTS_TO_IMAGE] = {
|
||||
id: LATENTS_TO_IMAGE,
|
||||
type: isUsingOnnxModel ? 'l2i_onnx' : 'l2i',
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
};
|
||||
|
||||
graph.nodes[CANVAS_OUTPUT] = {
|
||||
id: CANVAS_OUTPUT,
|
||||
type: 'img_resize',
|
||||
is_intermediate: !shouldAutoSave,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
@ -292,7 +289,7 @@ export const buildCanvasSDXLTextToImageGraph = (
|
||||
graph.nodes[CANVAS_OUTPUT] = {
|
||||
type: isUsingOnnxModel ? 'l2i_onnx' : 'l2i',
|
||||
id: CANVAS_OUTPUT,
|
||||
is_intermediate: !shouldAutoSave,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
};
|
||||
|
||||
@ -318,10 +315,10 @@ export const buildCanvasSDXLTextToImageGraph = (
|
||||
height: !isUsingScaledDimensions
|
||||
? height
|
||||
: scaledBoundingBoxDimensions.height,
|
||||
positive_prompt: '', // set in addDynamicPromptsToGraph
|
||||
positive_prompt: positivePrompt,
|
||||
negative_prompt: negativePrompt,
|
||||
model,
|
||||
seed: 0, // set in addDynamicPromptsToGraph
|
||||
seed,
|
||||
steps,
|
||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||
scheduler,
|
||||
@ -367,9 +364,6 @@ export const buildCanvasSDXLTextToImageGraph = (
|
||||
// optionally add custom VAE
|
||||
addVAEToGraph(state, graph, modelLoaderNodeId);
|
||||
|
||||
// add dynamic prompts - also sets up core iteration and seed
|
||||
addDynamicPromptsToGraph(state, graph);
|
||||
|
||||
// add controlnet, mutating `graph`
|
||||
addControlNetToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
|
||||
|
||||
@ -387,5 +381,7 @@ export const buildCanvasSDXLTextToImageGraph = (
|
||||
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
|
||||
}
|
||||
|
||||
addSaveImageNode(state, graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -7,10 +7,10 @@ import {
|
||||
ONNXTextToLatentsInvocation,
|
||||
} from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
import { addLoRAsToGraph } from './addLoRAsToGraph';
|
||||
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
|
||||
import { addSaveImageNode } from './addSaveImageNode';
|
||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||
@ -42,6 +42,7 @@ export const buildCanvasTextToImageGraph = (
|
||||
model,
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
seed,
|
||||
steps,
|
||||
vaePrecision,
|
||||
clipSkip,
|
||||
@ -54,14 +55,10 @@ export const buildCanvasTextToImageGraph = (
|
||||
// The bounding box determines width and height, not the width and height params
|
||||
const { width, height } = state.canvas.boundingBoxDimensions;
|
||||
|
||||
const {
|
||||
scaledBoundingBoxDimensions,
|
||||
boundingBoxScaleMethod,
|
||||
shouldAutoSave,
|
||||
} = state.canvas;
|
||||
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = state.canvas;
|
||||
|
||||
const fp32 = vaePrecision === 'fp32';
|
||||
|
||||
const is_intermediate = true;
|
||||
const isUsingScaledDimensions = ['auto', 'manual'].includes(
|
||||
boundingBoxScaleMethod
|
||||
);
|
||||
@ -90,7 +87,7 @@ export const buildCanvasTextToImageGraph = (
|
||||
? {
|
||||
type: 't2l_onnx',
|
||||
id: DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
@ -98,7 +95,7 @@ export const buildCanvasTextToImageGraph = (
|
||||
: {
|
||||
type: 'denoise_latents',
|
||||
id: DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
@ -123,31 +120,32 @@ export const buildCanvasTextToImageGraph = (
|
||||
[modelLoaderNodeId]: {
|
||||
type: modelLoaderNodeType,
|
||||
id: modelLoaderNodeId,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
model,
|
||||
},
|
||||
[CLIP_SKIP]: {
|
||||
type: 'clip_skip',
|
||||
id: CLIP_SKIP,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
skipped_layers: clipSkip,
|
||||
},
|
||||
[POSITIVE_CONDITIONING]: {
|
||||
type: isUsingOnnxModel ? 'prompt_onnx' : 'compel',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
prompt: positivePrompt,
|
||||
},
|
||||
[NEGATIVE_CONDITIONING]: {
|
||||
type: isUsingOnnxModel ? 'prompt_onnx' : 'compel',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
prompt: negativePrompt,
|
||||
},
|
||||
[NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
seed,
|
||||
width: !isUsingScaledDimensions
|
||||
? width
|
||||
: scaledBoundingBoxDimensions.width,
|
||||
@ -240,14 +238,14 @@ export const buildCanvasTextToImageGraph = (
|
||||
graph.nodes[LATENTS_TO_IMAGE] = {
|
||||
id: LATENTS_TO_IMAGE,
|
||||
type: isUsingOnnxModel ? 'l2i_onnx' : 'l2i',
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
};
|
||||
|
||||
graph.nodes[CANVAS_OUTPUT] = {
|
||||
id: CANVAS_OUTPUT,
|
||||
type: 'img_resize',
|
||||
is_intermediate: !shouldAutoSave,
|
||||
is_intermediate,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
@ -278,7 +276,7 @@ export const buildCanvasTextToImageGraph = (
|
||||
graph.nodes[CANVAS_OUTPUT] = {
|
||||
type: isUsingOnnxModel ? 'l2i_onnx' : 'l2i',
|
||||
id: CANVAS_OUTPUT,
|
||||
is_intermediate: !shouldAutoSave,
|
||||
is_intermediate,
|
||||
fp32,
|
||||
};
|
||||
|
||||
@ -304,10 +302,10 @@ export const buildCanvasTextToImageGraph = (
|
||||
height: !isUsingScaledDimensions
|
||||
? height
|
||||
: scaledBoundingBoxDimensions.height,
|
||||
positive_prompt: '', // set in addDynamicPromptsToGraph
|
||||
positive_prompt: positivePrompt,
|
||||
negative_prompt: negativePrompt,
|
||||
model,
|
||||
seed: 0, // set in addDynamicPromptsToGraph
|
||||
seed,
|
||||
steps,
|
||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||
scheduler,
|
||||
@ -340,9 +338,6 @@ export const buildCanvasTextToImageGraph = (
|
||||
// add LoRA support
|
||||
addLoRAsToGraph(state, graph, DENOISE_LATENTS, modelLoaderNodeId);
|
||||
|
||||
// add dynamic prompts - also sets up core iteration and seed
|
||||
addDynamicPromptsToGraph(state, graph);
|
||||
|
||||
// add controlnet, mutating `graph`
|
||||
addControlNetToLinearGraph(state, graph, DENOISE_LATENTS);
|
||||
|
||||
@ -360,5 +355,7 @@ export const buildCanvasTextToImageGraph = (
|
||||
addWatermarkerToGraph(state, graph, CANVAS_OUTPUT);
|
||||
}
|
||||
|
||||
addSaveImageNode(state, graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -0,0 +1,185 @@
|
||||
import { NUMPY_RAND_MAX } from 'app/constants';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { generateSeeds } from 'common/util/generateSeeds';
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { range, unset } from 'lodash-es';
|
||||
import { components } from 'services/api/schema';
|
||||
import { Batch, BatchConfig } from 'services/api/types';
|
||||
import {
|
||||
CANVAS_COHERENCE_NOISE,
|
||||
METADATA_ACCUMULATOR,
|
||||
NOISE,
|
||||
POSITIVE_CONDITIONING,
|
||||
} from './constants';
|
||||
|
||||
export const prepareLinearUIBatch = (
|
||||
state: RootState,
|
||||
graph: NonNullableGraph,
|
||||
prepend: boolean
|
||||
): BatchConfig => {
|
||||
const { iterations, model, shouldRandomizeSeed, seed } = state.generation;
|
||||
const { shouldConcatSDXLStylePrompt, positiveStylePrompt } = state.sdxl;
|
||||
const { prompts, seedBehaviour } = state.dynamicPrompts;
|
||||
|
||||
const data: Batch['data'] = [];
|
||||
|
||||
if (prompts.length === 1) {
|
||||
unset(graph.nodes[METADATA_ACCUMULATOR], 'seed');
|
||||
const seeds = generateSeeds({
|
||||
count: iterations,
|
||||
start: shouldRandomizeSeed ? undefined : seed,
|
||||
});
|
||||
|
||||
const zipped: components['schemas']['BatchDatum'][] = [];
|
||||
|
||||
if (graph.nodes[NOISE]) {
|
||||
zipped.push({
|
||||
node_path: NOISE,
|
||||
field_name: 'seed',
|
||||
items: seeds,
|
||||
});
|
||||
}
|
||||
|
||||
if (graph.nodes[METADATA_ACCUMULATOR]) {
|
||||
zipped.push({
|
||||
node_path: METADATA_ACCUMULATOR,
|
||||
field_name: 'seed',
|
||||
items: seeds,
|
||||
});
|
||||
}
|
||||
|
||||
if (graph.nodes[CANVAS_COHERENCE_NOISE]) {
|
||||
zipped.push({
|
||||
node_path: CANVAS_COHERENCE_NOISE,
|
||||
field_name: 'seed',
|
||||
items: seeds.map((seed) => (seed + 1) % NUMPY_RAND_MAX),
|
||||
});
|
||||
}
|
||||
|
||||
data.push(zipped);
|
||||
} else {
|
||||
// prompts.length > 1 aka dynamic prompts
|
||||
const firstBatchDatumList: components['schemas']['BatchDatum'][] = [];
|
||||
const secondBatchDatumList: components['schemas']['BatchDatum'][] = [];
|
||||
|
||||
// add seeds first to ensure the output order groups the prompts
|
||||
if (seedBehaviour === 'PER_PROMPT') {
|
||||
const seeds = generateSeeds({
|
||||
count: prompts.length * iterations,
|
||||
start: shouldRandomizeSeed ? undefined : seed,
|
||||
});
|
||||
|
||||
if (graph.nodes[NOISE]) {
|
||||
firstBatchDatumList.push({
|
||||
node_path: NOISE,
|
||||
field_name: 'seed',
|
||||
items: seeds,
|
||||
});
|
||||
}
|
||||
|
||||
if (graph.nodes[METADATA_ACCUMULATOR]) {
|
||||
firstBatchDatumList.push({
|
||||
node_path: METADATA_ACCUMULATOR,
|
||||
field_name: 'seed',
|
||||
items: seeds,
|
||||
});
|
||||
}
|
||||
|
||||
if (graph.nodes[CANVAS_COHERENCE_NOISE]) {
|
||||
firstBatchDatumList.push({
|
||||
node_path: CANVAS_COHERENCE_NOISE,
|
||||
field_name: 'seed',
|
||||
items: seeds.map((seed) => (seed + 1) % NUMPY_RAND_MAX),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// seedBehaviour = SeedBehaviour.PerRun
|
||||
const seeds = generateSeeds({
|
||||
count: iterations,
|
||||
start: shouldRandomizeSeed ? undefined : seed,
|
||||
});
|
||||
|
||||
if (graph.nodes[NOISE]) {
|
||||
secondBatchDatumList.push({
|
||||
node_path: NOISE,
|
||||
field_name: 'seed',
|
||||
items: seeds,
|
||||
});
|
||||
}
|
||||
if (graph.nodes[METADATA_ACCUMULATOR]) {
|
||||
secondBatchDatumList.push({
|
||||
node_path: METADATA_ACCUMULATOR,
|
||||
field_name: 'seed',
|
||||
items: seeds,
|
||||
});
|
||||
}
|
||||
if (graph.nodes[CANVAS_COHERENCE_NOISE]) {
|
||||
secondBatchDatumList.push({
|
||||
node_path: CANVAS_COHERENCE_NOISE,
|
||||
field_name: 'seed',
|
||||
items: seeds.map((seed) => (seed + 1) % NUMPY_RAND_MAX),
|
||||
});
|
||||
}
|
||||
data.push(secondBatchDatumList);
|
||||
}
|
||||
|
||||
const extendedPrompts =
|
||||
seedBehaviour === 'PER_PROMPT'
|
||||
? range(iterations).flatMap(() => prompts)
|
||||
: prompts;
|
||||
|
||||
// zipped batch of prompts
|
||||
if (graph.nodes[POSITIVE_CONDITIONING]) {
|
||||
firstBatchDatumList.push({
|
||||
node_path: POSITIVE_CONDITIONING,
|
||||
field_name: 'prompt',
|
||||
items: extendedPrompts,
|
||||
});
|
||||
}
|
||||
|
||||
if (graph.nodes[METADATA_ACCUMULATOR]) {
|
||||
firstBatchDatumList.push({
|
||||
node_path: METADATA_ACCUMULATOR,
|
||||
field_name: 'positive_prompt',
|
||||
items: extendedPrompts,
|
||||
});
|
||||
}
|
||||
|
||||
if (shouldConcatSDXLStylePrompt && model?.base_model === 'sdxl') {
|
||||
unset(graph.nodes[METADATA_ACCUMULATOR], 'positive_style_prompt');
|
||||
|
||||
const stylePrompts = extendedPrompts.map((p) =>
|
||||
[p, positiveStylePrompt].join(' ')
|
||||
);
|
||||
|
||||
if (graph.nodes[POSITIVE_CONDITIONING]) {
|
||||
firstBatchDatumList.push({
|
||||
node_path: POSITIVE_CONDITIONING,
|
||||
field_name: 'style',
|
||||
items: stylePrompts,
|
||||
});
|
||||
}
|
||||
|
||||
if (graph.nodes[METADATA_ACCUMULATOR]) {
|
||||
firstBatchDatumList.push({
|
||||
node_path: METADATA_ACCUMULATOR,
|
||||
field_name: 'positive_style_prompt',
|
||||
items: stylePrompts,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
data.push(firstBatchDatumList);
|
||||
}
|
||||
|
||||
const enqueueBatchArg: BatchConfig = {
|
||||
prepend,
|
||||
batch: {
|
||||
graph,
|
||||
runs: 1,
|
||||
data,
|
||||
},
|
||||
};
|
||||
|
||||
return enqueueBatchArg;
|
||||
};
|
@ -7,10 +7,10 @@ import {
|
||||
ImageToLatentsInvocation,
|
||||
} from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
import { addLoRAsToGraph } from './addLoRAsToGraph';
|
||||
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
|
||||
import { addSaveImageNode } from './addSaveImageNode';
|
||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||
@ -42,6 +42,7 @@ export const buildLinearImageToImageGraph = (
|
||||
model,
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
seed,
|
||||
steps,
|
||||
initialImage,
|
||||
img2imgStrength: strength,
|
||||
@ -56,16 +57,6 @@ export const buildLinearImageToImageGraph = (
|
||||
seamlessYAxis,
|
||||
} = state.generation;
|
||||
|
||||
// TODO: add batch functionality
|
||||
// const {
|
||||
// isEnabled: isBatchEnabled,
|
||||
// imageNames: batchImageNames,
|
||||
// asInitialImage,
|
||||
// } = state.batch;
|
||||
|
||||
// const shouldBatch =
|
||||
// isBatchEnabled && batchImageNames.length > 0 && asInitialImage;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -86,6 +77,7 @@ export const buildLinearImageToImageGraph = (
|
||||
}
|
||||
|
||||
const fp32 = vaePrecision === 'fp32';
|
||||
const is_intermediate = true;
|
||||
|
||||
let modelLoaderNodeId = MAIN_MODEL_LOADER;
|
||||
|
||||
@ -101,31 +93,38 @@ export const buildLinearImageToImageGraph = (
|
||||
type: 'main_model_loader',
|
||||
id: modelLoaderNodeId,
|
||||
model,
|
||||
is_intermediate,
|
||||
},
|
||||
[CLIP_SKIP]: {
|
||||
type: 'clip_skip',
|
||||
id: CLIP_SKIP,
|
||||
skipped_layers: clipSkip,
|
||||
is_intermediate,
|
||||
},
|
||||
[POSITIVE_CONDITIONING]: {
|
||||
type: 'compel',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
prompt: positivePrompt,
|
||||
is_intermediate,
|
||||
},
|
||||
[NEGATIVE_CONDITIONING]: {
|
||||
type: 'compel',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
prompt: negativePrompt,
|
||||
is_intermediate,
|
||||
},
|
||||
[NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
use_cpu,
|
||||
seed,
|
||||
is_intermediate,
|
||||
},
|
||||
[LATENTS_TO_IMAGE]: {
|
||||
type: 'l2i',
|
||||
id: LATENTS_TO_IMAGE,
|
||||
fp32,
|
||||
is_intermediate,
|
||||
},
|
||||
[DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
@ -135,6 +134,7 @@ export const buildLinearImageToImageGraph = (
|
||||
steps,
|
||||
denoising_start: 1 - strength,
|
||||
denoising_end: 1,
|
||||
is_intermediate,
|
||||
},
|
||||
[IMAGE_TO_LATENTS]: {
|
||||
type: 'i2l',
|
||||
@ -144,6 +144,7 @@ export const buildLinearImageToImageGraph = (
|
||||
// image_name: initialImage.image_name,
|
||||
// },
|
||||
fp32,
|
||||
is_intermediate,
|
||||
},
|
||||
},
|
||||
edges: [
|
||||
@ -321,10 +322,10 @@ export const buildLinearImageToImageGraph = (
|
||||
cfg_scale,
|
||||
height,
|
||||
width,
|
||||
positive_prompt: '', // set in addDynamicPromptsToGraph
|
||||
positive_prompt: positivePrompt,
|
||||
negative_prompt: negativePrompt,
|
||||
model,
|
||||
seed: 0, // set in addDynamicPromptsToGraph
|
||||
seed,
|
||||
steps,
|
||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||
scheduler,
|
||||
@ -359,9 +360,6 @@ export const buildLinearImageToImageGraph = (
|
||||
// add LoRA support
|
||||
addLoRAsToGraph(state, graph, DENOISE_LATENTS, modelLoaderNodeId);
|
||||
|
||||
// add dynamic prompts - also sets up core iteration and seed
|
||||
addDynamicPromptsToGraph(state, graph);
|
||||
|
||||
// add controlnet, mutating `graph`
|
||||
addControlNetToLinearGraph(state, graph, DENOISE_LATENTS);
|
||||
|
||||
@ -379,5 +377,7 @@ export const buildLinearImageToImageGraph = (
|
||||
addWatermarkerToGraph(state, graph);
|
||||
}
|
||||
|
||||
addSaveImageNode(state, graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -7,11 +7,11 @@ import {
|
||||
ImageToLatentsInvocation,
|
||||
} from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
|
||||
import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph';
|
||||
import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph';
|
||||
import { addSaveImageNode } from './addSaveImageNode';
|
||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||
@ -29,7 +29,7 @@ import {
|
||||
SDXL_REFINER_SEAMLESS,
|
||||
SEAMLESS,
|
||||
} from './constants';
|
||||
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
|
||||
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
||||
|
||||
/**
|
||||
* Builds the Image to Image tab graph.
|
||||
@ -44,6 +44,7 @@ export const buildLinearSDXLImageToImageGraph = (
|
||||
model,
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
seed,
|
||||
steps,
|
||||
initialImage,
|
||||
shouldFitToWidthHeight,
|
||||
@ -60,7 +61,6 @@ export const buildLinearSDXLImageToImageGraph = (
|
||||
const {
|
||||
positiveStylePrompt,
|
||||
negativeStylePrompt,
|
||||
shouldConcatSDXLStylePrompt,
|
||||
shouldUseSDXLRefiner,
|
||||
refinerStart,
|
||||
sdxlImg2ImgDenoisingStrength: strength,
|
||||
@ -86,6 +86,7 @@ export const buildLinearSDXLImageToImageGraph = (
|
||||
}
|
||||
|
||||
const fp32 = vaePrecision === 'fp32';
|
||||
const is_intermediate = true;
|
||||
|
||||
// Model Loader ID
|
||||
let modelLoaderNodeId = SDXL_MODEL_LOADER;
|
||||
@ -95,8 +96,8 @@ export const buildLinearSDXLImageToImageGraph = (
|
||||
: initialGenerationState.shouldUseCpuNoise;
|
||||
|
||||
// Construct Style Prompt
|
||||
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
|
||||
craftSDXLStylePrompt(state, shouldConcatSDXLStylePrompt);
|
||||
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
|
||||
buildSDXLStylePrompts(state);
|
||||
|
||||
// copy-pasted graph from node editor, filled in with state values & friendly node ids
|
||||
const graph: NonNullableGraph = {
|
||||
@ -106,28 +107,34 @@ export const buildLinearSDXLImageToImageGraph = (
|
||||
type: 'sdxl_model_loader',
|
||||
id: modelLoaderNodeId,
|
||||
model,
|
||||
is_intermediate,
|
||||
},
|
||||
[POSITIVE_CONDITIONING]: {
|
||||
type: 'sdxl_compel_prompt',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
prompt: positivePrompt,
|
||||
style: craftedPositiveStylePrompt,
|
||||
style: joinedPositiveStylePrompt,
|
||||
is_intermediate,
|
||||
},
|
||||
[NEGATIVE_CONDITIONING]: {
|
||||
type: 'sdxl_compel_prompt',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
prompt: negativePrompt,
|
||||
style: craftedNegativeStylePrompt,
|
||||
style: joinedNegativeStylePrompt,
|
||||
is_intermediate,
|
||||
},
|
||||
[NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
use_cpu,
|
||||
seed,
|
||||
is_intermediate,
|
||||
},
|
||||
[LATENTS_TO_IMAGE]: {
|
||||
type: 'l2i',
|
||||
id: LATENTS_TO_IMAGE,
|
||||
fp32,
|
||||
is_intermediate,
|
||||
},
|
||||
[SDXL_DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
@ -139,6 +146,7 @@ export const buildLinearSDXLImageToImageGraph = (
|
||||
? Math.min(refinerStart, 1 - strength)
|
||||
: 1 - strength,
|
||||
denoising_end: shouldUseSDXLRefiner ? refinerStart : 1,
|
||||
is_intermediate,
|
||||
},
|
||||
[IMAGE_TO_LATENTS]: {
|
||||
type: 'i2l',
|
||||
@ -148,6 +156,7 @@ export const buildLinearSDXLImageToImageGraph = (
|
||||
// image_name: initialImage.image_name,
|
||||
// },
|
||||
fp32,
|
||||
is_intermediate,
|
||||
},
|
||||
},
|
||||
edges: [
|
||||
@ -334,10 +343,10 @@ export const buildLinearSDXLImageToImageGraph = (
|
||||
cfg_scale,
|
||||
height,
|
||||
width,
|
||||
positive_prompt: '', // set in addDynamicPromptsToGraph
|
||||
positive_prompt: positivePrompt,
|
||||
negative_prompt: negativePrompt,
|
||||
model,
|
||||
seed: 0, // set in addDynamicPromptsToGraph
|
||||
seed,
|
||||
steps,
|
||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||
scheduler,
|
||||
@ -388,9 +397,6 @@ export const buildLinearSDXLImageToImageGraph = (
|
||||
// Add IP Adapter
|
||||
addIPAdapterToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
|
||||
|
||||
// add dynamic prompts - also sets up core iteration and seed
|
||||
addDynamicPromptsToGraph(state, graph);
|
||||
|
||||
// NSFW & watermark - must be last thing added to graph
|
||||
if (state.system.shouldUseNSFWChecker) {
|
||||
// must add before watermarker!
|
||||
@ -402,5 +408,7 @@ export const buildLinearSDXLImageToImageGraph = (
|
||||
addWatermarkerToGraph(state, graph);
|
||||
}
|
||||
|
||||
addSaveImageNode(state, graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -3,11 +3,11 @@ import { RootState } from 'app/store/store';
|
||||
import { NonNullableGraph } from 'features/nodes/types/types';
|
||||
import { initialGenerationState } from 'features/parameters/store/generationSlice';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
|
||||
import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph';
|
||||
import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph';
|
||||
import { addSaveImageNode } from './addSaveImageNode';
|
||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||
@ -23,7 +23,7 @@ import {
|
||||
SDXL_TEXT_TO_IMAGE_GRAPH,
|
||||
SEAMLESS,
|
||||
} from './constants';
|
||||
import { craftSDXLStylePrompt } from './helpers/craftSDXLStylePrompt';
|
||||
import { buildSDXLStylePrompts } from './helpers/craftSDXLStylePrompt';
|
||||
|
||||
export const buildLinearSDXLTextToImageGraph = (
|
||||
state: RootState
|
||||
@ -35,6 +35,7 @@ export const buildLinearSDXLTextToImageGraph = (
|
||||
model,
|
||||
cfgScale: cfg_scale,
|
||||
scheduler,
|
||||
seed,
|
||||
steps,
|
||||
width,
|
||||
height,
|
||||
@ -50,24 +51,23 @@ export const buildLinearSDXLTextToImageGraph = (
|
||||
positiveStylePrompt,
|
||||
negativeStylePrompt,
|
||||
shouldUseSDXLRefiner,
|
||||
shouldConcatSDXLStylePrompt,
|
||||
refinerStart,
|
||||
} = state.sdxl;
|
||||
|
||||
const use_cpu = shouldUseNoiseSettings
|
||||
? shouldUseCpuNoise
|
||||
: initialGenerationState.shouldUseCpuNoise;
|
||||
|
||||
if (!model) {
|
||||
log.error('No model found in state');
|
||||
throw new Error('No model found in state');
|
||||
}
|
||||
|
||||
const fp32 = vaePrecision === 'fp32';
|
||||
const is_intermediate = true;
|
||||
|
||||
// Construct Style Prompt
|
||||
const { craftedPositiveStylePrompt, craftedNegativeStylePrompt } =
|
||||
craftSDXLStylePrompt(state, shouldConcatSDXLStylePrompt);
|
||||
const { joinedPositiveStylePrompt, joinedNegativeStylePrompt } =
|
||||
buildSDXLStylePrompts(state);
|
||||
|
||||
// Model Loader ID
|
||||
let modelLoaderNodeId = SDXL_MODEL_LOADER;
|
||||
@ -89,25 +89,30 @@ export const buildLinearSDXLTextToImageGraph = (
|
||||
type: 'sdxl_model_loader',
|
||||
id: modelLoaderNodeId,
|
||||
model,
|
||||
is_intermediate,
|
||||
},
|
||||
[POSITIVE_CONDITIONING]: {
|
||||
type: 'sdxl_compel_prompt',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
prompt: positivePrompt,
|
||||
style: craftedPositiveStylePrompt,
|
||||
style: joinedPositiveStylePrompt,
|
||||
is_intermediate,
|
||||
},
|
||||
[NEGATIVE_CONDITIONING]: {
|
||||
type: 'sdxl_compel_prompt',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
prompt: negativePrompt,
|
||||
style: craftedNegativeStylePrompt,
|
||||
style: joinedNegativeStylePrompt,
|
||||
is_intermediate,
|
||||
},
|
||||
[NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
seed,
|
||||
width,
|
||||
height,
|
||||
use_cpu,
|
||||
is_intermediate,
|
||||
},
|
||||
[SDXL_DENOISE_LATENTS]: {
|
||||
type: 'denoise_latents',
|
||||
@ -117,11 +122,13 @@ export const buildLinearSDXLTextToImageGraph = (
|
||||
steps,
|
||||
denoising_start: 0,
|
||||
denoising_end: shouldUseSDXLRefiner ? refinerStart : 1,
|
||||
is_intermediate,
|
||||
},
|
||||
[LATENTS_TO_IMAGE]: {
|
||||
type: 'l2i',
|
||||
id: LATENTS_TO_IMAGE,
|
||||
fp32,
|
||||
is_intermediate,
|
||||
},
|
||||
},
|
||||
edges: [
|
||||
@ -229,10 +236,10 @@ export const buildLinearSDXLTextToImageGraph = (
|
||||
cfg_scale,
|
||||
height,
|
||||
width,
|
||||
positive_prompt: '', // set in addDynamicPromptsToGraph
|
||||
positive_prompt: positivePrompt,
|
||||
negative_prompt: negativePrompt,
|
||||
model,
|
||||
seed: 0, // set in addDynamicPromptsToGraph
|
||||
seed,
|
||||
steps,
|
||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||
scheduler,
|
||||
@ -281,9 +288,6 @@ export const buildLinearSDXLTextToImageGraph = (
|
||||
// add IP Adapter
|
||||
addIPAdapterToLinearGraph(state, graph, SDXL_DENOISE_LATENTS);
|
||||
|
||||
// add dynamic prompts - also sets up core iteration and seed
|
||||
addDynamicPromptsToGraph(state, graph);
|
||||
|
||||
// NSFW & watermark - must be last thing added to graph
|
||||
if (state.system.shouldUseNSFWChecker) {
|
||||
// must add before watermarker!
|
||||
@ -295,5 +299,7 @@ export const buildLinearSDXLTextToImageGraph = (
|
||||
addWatermarkerToGraph(state, graph);
|
||||
}
|
||||
|
||||
addSaveImageNode(state, graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -7,10 +7,10 @@ import {
|
||||
ONNXTextToLatentsInvocation,
|
||||
} from 'services/api/types';
|
||||
import { addControlNetToLinearGraph } from './addControlNetToLinearGraph';
|
||||
import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph';
|
||||
import { addIPAdapterToLinearGraph } from './addIPAdapterToLinearGraph';
|
||||
import { addLoRAsToGraph } from './addLoRAsToGraph';
|
||||
import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph';
|
||||
import { addSaveImageNode } from './addSaveImageNode';
|
||||
import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph';
|
||||
import { addVAEToGraph } from './addVAEToGraph';
|
||||
import { addWatermarkerToGraph } from './addWatermarkerToGraph';
|
||||
@ -47,6 +47,7 @@ export const buildLinearTextToImageGraph = (
|
||||
vaePrecision,
|
||||
seamlessXAxis,
|
||||
seamlessYAxis,
|
||||
seed,
|
||||
} = state.generation;
|
||||
|
||||
const use_cpu = shouldUseNoiseSettings
|
||||
@ -59,7 +60,7 @@ export const buildLinearTextToImageGraph = (
|
||||
}
|
||||
|
||||
const fp32 = vaePrecision === 'fp32';
|
||||
|
||||
const is_intermediate = true;
|
||||
const isUsingOnnxModel = model.model_type === 'onnx';
|
||||
|
||||
let modelLoaderNodeId = isUsingOnnxModel
|
||||
@ -75,7 +76,7 @@ export const buildLinearTextToImageGraph = (
|
||||
? {
|
||||
type: 't2l_onnx',
|
||||
id: DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
@ -83,7 +84,7 @@ export const buildLinearTextToImageGraph = (
|
||||
: {
|
||||
type: 'denoise_latents',
|
||||
id: DENOISE_LATENTS,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
cfg_scale,
|
||||
scheduler,
|
||||
steps,
|
||||
@ -109,40 +110,42 @@ export const buildLinearTextToImageGraph = (
|
||||
[modelLoaderNodeId]: {
|
||||
type: modelLoaderNodeType,
|
||||
id: modelLoaderNodeId,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
model,
|
||||
},
|
||||
[CLIP_SKIP]: {
|
||||
type: 'clip_skip',
|
||||
id: CLIP_SKIP,
|
||||
skipped_layers: clipSkip,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
},
|
||||
[POSITIVE_CONDITIONING]: {
|
||||
type: isUsingOnnxModel ? 'prompt_onnx' : 'compel',
|
||||
id: POSITIVE_CONDITIONING,
|
||||
prompt: positivePrompt,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
},
|
||||
[NEGATIVE_CONDITIONING]: {
|
||||
type: isUsingOnnxModel ? 'prompt_onnx' : 'compel',
|
||||
id: NEGATIVE_CONDITIONING,
|
||||
prompt: negativePrompt,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
},
|
||||
[NOISE]: {
|
||||
type: 'noise',
|
||||
id: NOISE,
|
||||
seed,
|
||||
width,
|
||||
height,
|
||||
use_cpu,
|
||||
is_intermediate: true,
|
||||
is_intermediate,
|
||||
},
|
||||
[t2lNode.id]: t2lNode,
|
||||
[LATENTS_TO_IMAGE]: {
|
||||
type: isUsingOnnxModel ? 'l2i_onnx' : 'l2i',
|
||||
id: LATENTS_TO_IMAGE,
|
||||
fp32,
|
||||
is_intermediate,
|
||||
},
|
||||
},
|
||||
edges: [
|
||||
@ -241,10 +244,10 @@ export const buildLinearTextToImageGraph = (
|
||||
cfg_scale,
|
||||
height,
|
||||
width,
|
||||
positive_prompt: '', // set in addDynamicPromptsToGraph
|
||||
positive_prompt: positivePrompt,
|
||||
negative_prompt: negativePrompt,
|
||||
model,
|
||||
seed: 0, // set in addDynamicPromptsToGraph
|
||||
seed,
|
||||
steps,
|
||||
rand_device: use_cpu ? 'cpu' : 'cuda',
|
||||
scheduler,
|
||||
@ -277,9 +280,6 @@ export const buildLinearTextToImageGraph = (
|
||||
// add LoRA support
|
||||
addLoRAsToGraph(state, graph, DENOISE_LATENTS, modelLoaderNodeId);
|
||||
|
||||
// add dynamic prompts - also sets up core iteration and seed
|
||||
addDynamicPromptsToGraph(state, graph);
|
||||
|
||||
// add controlnet, mutating `graph`
|
||||
addControlNetToLinearGraph(state, graph, DENOISE_LATENTS);
|
||||
|
||||
@ -297,5 +297,7 @@ export const buildLinearTextToImageGraph = (
|
||||
addWatermarkerToGraph(state, graph);
|
||||
}
|
||||
|
||||
addSaveImageNode(state, graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
@ -55,6 +55,9 @@ export const buildNodesGraph = (nodesState: NodesState): Graph => {
|
||||
{} as Record<Exclude<string, 'id' | 'type'>, unknown>
|
||||
);
|
||||
|
||||
// add reserved use_cache
|
||||
transformedInputs['use_cache'] = node.data.useCache;
|
||||
|
||||
// Build this specific node
|
||||
const graphNode = {
|
||||
type,
|
||||
|
@ -3,6 +3,7 @@ export const POSITIVE_CONDITIONING = 'positive_conditioning';
|
||||
export const NEGATIVE_CONDITIONING = 'negative_conditioning';
|
||||
export const DENOISE_LATENTS = 'denoise_latents';
|
||||
export const LATENTS_TO_IMAGE = 'latents_to_image';
|
||||
export const SAVE_IMAGE = 'save_image';
|
||||
export const NSFW_CHECKER = 'nsfw_checker';
|
||||
export const WATERMARKER = 'invisible_watermark';
|
||||
export const NOISE = 'noise';
|
||||
|
@ -1,28 +1,26 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
|
||||
export const craftSDXLStylePrompt = (
|
||||
export const buildSDXLStylePrompts = (
|
||||
state: RootState,
|
||||
shouldConcatSDXLStylePrompt: boolean
|
||||
overrideConcat?: boolean
|
||||
) => {
|
||||
const { positivePrompt, negativePrompt } = state.generation;
|
||||
const { positiveStylePrompt, negativeStylePrompt } = state.sdxl;
|
||||
const {
|
||||
positiveStylePrompt,
|
||||
negativeStylePrompt,
|
||||
shouldConcatSDXLStylePrompt,
|
||||
} = state.sdxl;
|
||||
|
||||
let craftedPositiveStylePrompt = positiveStylePrompt;
|
||||
let craftedNegativeStylePrompt = negativeStylePrompt;
|
||||
// Construct Style Prompt
|
||||
const joinedPositiveStylePrompt =
|
||||
shouldConcatSDXLStylePrompt || overrideConcat
|
||||
? [positivePrompt, positiveStylePrompt].join(' ')
|
||||
: positiveStylePrompt;
|
||||
|
||||
if (shouldConcatSDXLStylePrompt) {
|
||||
if (positiveStylePrompt.length > 0) {
|
||||
craftedPositiveStylePrompt = `${positivePrompt} ${positiveStylePrompt}`;
|
||||
} else {
|
||||
craftedPositiveStylePrompt = positivePrompt;
|
||||
}
|
||||
const joinedNegativeStylePrompt =
|
||||
shouldConcatSDXLStylePrompt || overrideConcat
|
||||
? [negativePrompt, negativeStylePrompt].join(' ')
|
||||
: negativeStylePrompt;
|
||||
|
||||
if (negativeStylePrompt.length > 0) {
|
||||
craftedNegativeStylePrompt = `${negativePrompt} ${negativeStylePrompt}`;
|
||||
} else {
|
||||
craftedNegativeStylePrompt = negativePrompt;
|
||||
}
|
||||
}
|
||||
|
||||
return { craftedPositiveStylePrompt, craftedNegativeStylePrompt };
|
||||
return { joinedPositiveStylePrompt, joinedNegativeStylePrompt };
|
||||
};
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
} from '../types/types';
|
||||
import { buildInputFieldTemplate, getFieldType } from './fieldTemplateBuilders';
|
||||
|
||||
const RESERVED_INPUT_FIELD_NAMES = ['id', 'type', 'metadata'];
|
||||
const RESERVED_INPUT_FIELD_NAMES = ['id', 'type', 'metadata', 'use_cache'];
|
||||
const RESERVED_OUTPUT_FIELD_NAMES = ['type'];
|
||||
const RESERVED_FIELD_TYPES = [
|
||||
'WorkflowField',
|
||||
@ -235,6 +235,8 @@ export const parseSchema = (
|
||||
{} as Record<string, OutputFieldTemplate>
|
||||
);
|
||||
|
||||
const useCache = schema.properties.use_cache.default;
|
||||
|
||||
const invocation: InvocationTemplate = {
|
||||
title,
|
||||
type,
|
||||
@ -244,6 +246,7 @@ export const parseSchema = (
|
||||
outputType,
|
||||
inputs,
|
||||
outputs,
|
||||
useCache,
|
||||
};
|
||||
|
||||
Object.assign(invocationsAccumulator, { [type]: invocation });
|
||||
|
@ -73,7 +73,7 @@ export const validateWorkflow = (
|
||||
!(edge.sourceHandle in sourceNode.data.outputs)
|
||||
) {
|
||||
issues.push(
|
||||
`${i18n.t('nodes.outputNodes')} "${edge.source}.${
|
||||
`${i18n.t('nodes.outputNode')} "${edge.source}.${
|
||||
edge.sourceHandle
|
||||
}" ${i18n.t('nodes.doesNotExist')}`
|
||||
);
|
||||
@ -89,7 +89,7 @@ export const validateWorkflow = (
|
||||
!(edge.targetHandle in targetNode.data.inputs)
|
||||
) {
|
||||
issues.push(
|
||||
`${i18n.t('nodes.inputFeilds')} "${edge.target}.${
|
||||
`${i18n.t('nodes.inputField')} "${edge.target}.${
|
||||
edge.targetHandle
|
||||
}" ${i18n.t('nodes.doesNotExist')}`
|
||||
);
|
||||
|
@ -15,8 +15,6 @@ const selector = createSelector(
|
||||
state.config.sd.iterations;
|
||||
const { iterations } = state.generation;
|
||||
const { shouldUseSliders } = state.ui;
|
||||
const isDisabled =
|
||||
state.dynamicPrompts.isEnabled && state.dynamicPrompts.combinatorial;
|
||||
|
||||
const step = state.hotkeys.shift ? fineStep : coarseStep;
|
||||
|
||||
@ -28,13 +26,16 @@ const selector = createSelector(
|
||||
inputMax,
|
||||
step,
|
||||
shouldUseSliders,
|
||||
isDisabled,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const ParamIterations = () => {
|
||||
type Props = {
|
||||
asSlider?: boolean;
|
||||
};
|
||||
|
||||
const ParamIterations = ({ asSlider }: Props) => {
|
||||
const {
|
||||
iterations,
|
||||
initial,
|
||||
@ -43,7 +44,6 @@ const ParamIterations = () => {
|
||||
inputMax,
|
||||
step,
|
||||
shouldUseSliders,
|
||||
isDisabled,
|
||||
} = useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
@ -59,10 +59,9 @@ const ParamIterations = () => {
|
||||
dispatch(setIterations(initial));
|
||||
}, [dispatch, initial]);
|
||||
|
||||
return shouldUseSliders ? (
|
||||
return asSlider || shouldUseSliders ? (
|
||||
<IAISlider
|
||||
isDisabled={isDisabled}
|
||||
label={t('parameters.images')}
|
||||
label={t('parameters.iterations')}
|
||||
step={step}
|
||||
min={min}
|
||||
max={sliderMax}
|
||||
@ -76,8 +75,7 @@ const ParamIterations = () => {
|
||||
/>
|
||||
) : (
|
||||
<IAINumberInput
|
||||
isDisabled={isDisabled}
|
||||
label={t('parameters.images')}
|
||||
label={t('parameters.iterations')}
|
||||
step={step}
|
||||
min={min}
|
||||
max={inputMax}
|
||||
|
@ -1,32 +1,23 @@
|
||||
import { Box, FormControl, useDisclosure } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { ChangeEvent, KeyboardEvent, memo, useCallback, useRef } from 'react';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import {
|
||||
clampSymmetrySteps,
|
||||
setPositivePrompt,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
|
||||
import { userInvoked } from 'app/store/actions';
|
||||
import IAITextarea from 'common/components/IAITextarea';
|
||||
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
|
||||
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
|
||||
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
|
||||
import { setPositivePrompt } from 'features/parameters/store/generationSlice';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { ChangeEvent, KeyboardEvent, memo, useCallback, useRef } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
|
||||
|
||||
const promptInputSelector = createSelector(
|
||||
[stateSelector, activeTabNameSelector],
|
||||
({ generation }, activeTabName) => {
|
||||
[stateSelector],
|
||||
({ generation }) => {
|
||||
return {
|
||||
prompt: generation.positivePrompt,
|
||||
activeTabName,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -41,8 +32,7 @@ const promptInputSelector = createSelector(
|
||||
*/
|
||||
const ParamPositiveConditioning = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { prompt, activeTabName } = useAppSelector(promptInputSelector);
|
||||
const isReady = useIsReadyToInvoke();
|
||||
const { prompt } = useAppSelector(promptInputSelector);
|
||||
const promptRef = useRef<HTMLTextAreaElement>(null);
|
||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||
const { t } = useTranslation();
|
||||
@ -104,23 +94,13 @@ const ParamPositiveConditioning = () => {
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Enter' && e.shiftKey === false && isReady) {
|
||||
e.preventDefault();
|
||||
dispatch(clampSymmetrySteps());
|
||||
dispatch(userInvoked(activeTabName));
|
||||
}
|
||||
if (isEmbeddingEnabled && e.key === '<') {
|
||||
onOpen();
|
||||
}
|
||||
},
|
||||
[isReady, dispatch, activeTabName, onOpen, isEmbeddingEnabled]
|
||||
[onOpen, isEmbeddingEnabled]
|
||||
);
|
||||
|
||||
// const handleSelect = (e: MouseEvent<HTMLTextAreaElement>) => {
|
||||
// const target = e.target as HTMLTextAreaElement;
|
||||
// setCaret({ start: target.selectionStart, end: target.selectionEnd });
|
||||
// };
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
<FormControl>
|
||||
|
@ -16,6 +16,7 @@ import { activeTabNameSelector } from '../../../../ui/store/uiSelectors';
|
||||
import ParamAspectRatio, { mappedAspectRatios } from './ParamAspectRatio';
|
||||
import ParamHeight from './ParamHeight';
|
||||
import ParamWidth from './ParamWidth';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
|
||||
const sizeOptsSelector = createSelector(
|
||||
[generationSelector, activeTabNameSelector],
|
||||
@ -30,7 +31,8 @@ const sizeOptsSelector = createSelector(
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
export default function ParamSize() {
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Flex, useDisclosure } from '@chakra-ui/react';
|
||||
import { upscaleRequested } from 'app/store/middleware/listenerMiddleware/listeners/upscaleRequested';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||
import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaExpandArrowsAlt } from 'react-icons/fa';
|
||||
import { FaExpand } from 'react-icons/fa';
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
import ParamESRGANModel from './ParamRealESRGANModel';
|
||||
|
||||
@ -16,7 +16,7 @@ type Props = { imageDTO?: ImageDTO };
|
||||
const ParamUpscalePopover = (props: Props) => {
|
||||
const { imageDTO } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const isBusy = useAppSelector(selectIsBusy);
|
||||
const inProgress = useIsQueueMutationInProgress();
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
@ -34,8 +34,9 @@ const ParamUpscalePopover = (props: Props) => {
|
||||
onClose={onClose}
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
tooltip={t('parameters.upscale')}
|
||||
onClick={onOpen}
|
||||
icon={<FaExpandArrowsAlt />}
|
||||
icon={<FaExpand />}
|
||||
aria-label={t('parameters.upscale')}
|
||||
/>
|
||||
}
|
||||
@ -49,7 +50,7 @@ const ParamUpscalePopover = (props: Props) => {
|
||||
<ParamESRGANModel />
|
||||
<IAIButton
|
||||
size="sm"
|
||||
isDisabled={!imageDTO || isBusy}
|
||||
isDisabled={!imageDTO || inProgress}
|
||||
onClick={handleClickUpscale}
|
||||
>
|
||||
{t('parameters.upscaleImage')}
|
||||
|
@ -1,184 +0,0 @@
|
||||
import {
|
||||
ButtonGroup,
|
||||
ButtonProps,
|
||||
ButtonSpinner,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItemOption,
|
||||
MenuList,
|
||||
MenuOptionGroup,
|
||||
} from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import {
|
||||
CancelStrategy,
|
||||
SystemState,
|
||||
cancelScheduled,
|
||||
cancelTypeChanged,
|
||||
} from 'features/system/store/systemSlice';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MdCancel, MdCancelScheduleSend } from 'react-icons/md';
|
||||
|
||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import { sessionCanceled } from 'services/api/thunks/session';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
|
||||
const cancelButtonSelector = createSelector(
|
||||
systemSelector,
|
||||
(system: SystemState) => {
|
||||
return {
|
||||
isProcessing: system.isProcessing,
|
||||
isConnected: system.isConnected,
|
||||
isCancelable: system.isCancelable,
|
||||
currentIteration: system.currentIteration,
|
||||
totalIterations: system.totalIterations,
|
||||
sessionId: system.sessionId,
|
||||
cancelType: system.cancelType,
|
||||
isCancelScheduled: system.isCancelScheduled,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
type Props = Omit<ButtonProps, 'aria-label'> & {
|
||||
btnGroupWidth?: string | number;
|
||||
asIconButton?: boolean;
|
||||
};
|
||||
|
||||
const CancelButton = (props: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { btnGroupWidth = 'auto', asIconButton = false, ...rest } = props;
|
||||
const {
|
||||
isProcessing,
|
||||
isConnected,
|
||||
isCancelable,
|
||||
cancelType,
|
||||
isCancelScheduled,
|
||||
sessionId,
|
||||
} = useAppSelector(cancelButtonSelector);
|
||||
|
||||
const handleClickCancel = useCallback(() => {
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cancelType === 'scheduled') {
|
||||
dispatch(cancelScheduled());
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(sessionCanceled({ session_id: sessionId }));
|
||||
}, [dispatch, sessionId, cancelType]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCancelTypeChanged = useCallback(
|
||||
(value: string | string[]) => {
|
||||
const newCancelType = Array.isArray(value) ? value[0] : value;
|
||||
dispatch(cancelTypeChanged(newCancelType as CancelStrategy));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'shift+x',
|
||||
() => {
|
||||
if ((isConnected || isProcessing) && isCancelable) {
|
||||
handleClickCancel();
|
||||
}
|
||||
},
|
||||
[isConnected, isProcessing, isCancelable]
|
||||
);
|
||||
|
||||
const cancelLabel = useMemo(() => {
|
||||
if (isCancelScheduled) {
|
||||
return t('parameters.cancel.isScheduled');
|
||||
}
|
||||
if (cancelType === 'immediate') {
|
||||
return t('parameters.cancel.immediate');
|
||||
}
|
||||
|
||||
return t('parameters.cancel.schedule');
|
||||
}, [t, cancelType, isCancelScheduled]);
|
||||
|
||||
const cancelIcon = useMemo(() => {
|
||||
if (isCancelScheduled) {
|
||||
return <ButtonSpinner />;
|
||||
}
|
||||
if (cancelType === 'immediate') {
|
||||
return <MdCancel />;
|
||||
}
|
||||
|
||||
return <MdCancelScheduleSend />;
|
||||
}, [cancelType, isCancelScheduled]);
|
||||
|
||||
return (
|
||||
<ButtonGroup isAttached width={btnGroupWidth}>
|
||||
{asIconButton ? (
|
||||
<IAIIconButton
|
||||
icon={cancelIcon}
|
||||
tooltip={cancelLabel}
|
||||
aria-label={cancelLabel}
|
||||
isDisabled={!isConnected || !isProcessing || !isCancelable}
|
||||
onClick={handleClickCancel}
|
||||
colorScheme="error"
|
||||
id="cancel-button"
|
||||
{...rest}
|
||||
/>
|
||||
) : (
|
||||
<IAIButton
|
||||
leftIcon={cancelIcon}
|
||||
tooltip={cancelLabel}
|
||||
aria-label={cancelLabel}
|
||||
isDisabled={!isConnected || !isProcessing || !isCancelable}
|
||||
onClick={handleClickCancel}
|
||||
colorScheme="error"
|
||||
id="cancel-button"
|
||||
{...rest}
|
||||
>
|
||||
{t('parameters.cancel.cancel')}
|
||||
</IAIButton>
|
||||
)}
|
||||
<Menu closeOnSelect={false}>
|
||||
<MenuButton
|
||||
as={IAIIconButton}
|
||||
tooltip={t('parameters.cancel.setType')}
|
||||
aria-label={t('parameters.cancel.setType')}
|
||||
icon={<ChevronDownIcon w="1em" h="1em" />}
|
||||
paddingX={0}
|
||||
paddingY={0}
|
||||
colorScheme="error"
|
||||
minWidth={5}
|
||||
{...rest}
|
||||
/>
|
||||
<MenuList minWidth="240px">
|
||||
<MenuOptionGroup
|
||||
value={cancelType}
|
||||
title="Cancel Type"
|
||||
type="radio"
|
||||
onChange={handleCancelTypeChanged}
|
||||
>
|
||||
<MenuItemOption value="immediate">
|
||||
{t('parameters.cancel.immediate')}
|
||||
</MenuItemOption>
|
||||
<MenuItemOption value="scheduled">
|
||||
{t('parameters.cancel.schedule')}
|
||||
</MenuItemOption>
|
||||
</MenuOptionGroup>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(CancelButton);
|
@ -1,174 +0,0 @@
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
Flex,
|
||||
ListItem,
|
||||
Text,
|
||||
UnorderedList,
|
||||
} from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { userInvoked } from 'app/store/actions';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import IAIButton, { IAIButtonProps } from 'common/components/IAIButton';
|
||||
import IAIIconButton, {
|
||||
IAIIconButtonProps,
|
||||
} from 'common/components/IAIIconButton';
|
||||
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
|
||||
import { clampSymmetrySteps } from 'features/parameters/store/generationSlice';
|
||||
import ProgressBar from 'features/system/components/ProgressBar';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaPlay } from 'react-icons/fa';
|
||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||
|
||||
interface InvokeButton
|
||||
extends Omit<IAIButtonProps | IAIIconButtonProps, 'aria-label'> {
|
||||
asIconButton?: boolean;
|
||||
}
|
||||
|
||||
export default function InvokeButton(props: InvokeButton) {
|
||||
const { asIconButton = false, sx, ...rest } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const { isReady, isProcessing } = useIsReadyToInvoke();
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
const handleInvoke = useCallback(() => {
|
||||
dispatch(clampSymmetrySteps());
|
||||
dispatch(userInvoked(activeTabName));
|
||||
}, [dispatch, activeTabName]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useHotkeys(
|
||||
['ctrl+enter', 'meta+enter'],
|
||||
handleInvoke,
|
||||
{
|
||||
enabled: () => isReady,
|
||||
preventDefault: true,
|
||||
enableOnFormTags: ['input', 'textarea', 'select'],
|
||||
},
|
||||
[isReady, activeTabName]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box style={{ flexGrow: 4 }} position="relative">
|
||||
<Box style={{ position: 'relative' }}>
|
||||
{!isReady && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: '0',
|
||||
left: '0',
|
||||
right: '0',
|
||||
height: '100%',
|
||||
overflow: 'clip',
|
||||
borderRadius: 'base',
|
||||
...sx,
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<ProgressBar />
|
||||
</Box>
|
||||
)}
|
||||
{asIconButton ? (
|
||||
<IAIIconButton
|
||||
aria-label={t('parameters.invoke.invoke')}
|
||||
type="submit"
|
||||
icon={<FaPlay />}
|
||||
isDisabled={!isReady}
|
||||
onClick={handleInvoke}
|
||||
tooltip={<InvokeButtonTooltipContent />}
|
||||
colorScheme="accent"
|
||||
isLoading={isProcessing}
|
||||
id="invoke-button"
|
||||
data-progress={isProcessing}
|
||||
sx={{
|
||||
w: 'full',
|
||||
flexGrow: 1,
|
||||
...sx,
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
) : (
|
||||
<IAIButton
|
||||
tooltip={<InvokeButtonTooltipContent />}
|
||||
aria-label={t('parameters.invoke.invoke')}
|
||||
type="submit"
|
||||
data-progress={isProcessing}
|
||||
isDisabled={!isReady}
|
||||
onClick={handleInvoke}
|
||||
colorScheme="accent"
|
||||
id="invoke-button"
|
||||
leftIcon={isProcessing ? undefined : <FaPlay />}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('parameters.invoke.invoke')}
|
||||
sx={{
|
||||
w: 'full',
|
||||
flexGrow: 1,
|
||||
fontWeight: 700,
|
||||
...sx,
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
Invoke
|
||||
</IAIButton>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const tooltipSelector = createSelector(
|
||||
[stateSelector],
|
||||
({ gallery }) => {
|
||||
const { autoAddBoardId } = gallery;
|
||||
|
||||
return {
|
||||
autoAddBoardId,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
export const InvokeButtonTooltipContent = memo(() => {
|
||||
const { isReady, reasons } = useIsReadyToInvoke();
|
||||
const { autoAddBoardId } = useAppSelector(tooltipSelector);
|
||||
const autoAddBoardName = useBoardName(autoAddBoardId);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={1}>
|
||||
<Text fontWeight={600}>
|
||||
{isReady
|
||||
? t('parameters.invoke.readyToInvoke')
|
||||
: t('parameters.invoke.unableToInvoke')}
|
||||
</Text>
|
||||
{reasons.length > 0 && (
|
||||
<UnorderedList>
|
||||
{reasons.map((reason, i) => (
|
||||
<ListItem key={`${reason}.${i}`}>
|
||||
<Text fontWeight={400}>{reason}</Text>
|
||||
</ListItem>
|
||||
))}
|
||||
</UnorderedList>
|
||||
)}
|
||||
<Divider
|
||||
opacity={0.2}
|
||||
borderColor="base.50"
|
||||
_dark={{ borderColor: 'base.900' }}
|
||||
/>
|
||||
<Text fontWeight={400} fontStyle="oblique 10deg">
|
||||
{t('parameters.invoke.addingImagesTo')}{' '}
|
||||
<Text as="span" fontWeight={600}>
|
||||
{autoAddBoardName || 'Uncategorized'}
|
||||
</Text>
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
InvokeButtonTooltipContent.displayName = 'InvokeButtonTooltipContent';
|
@ -1,18 +0,0 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import CancelButton from './CancelButton';
|
||||
import InvokeButton from './InvokeButton';
|
||||
|
||||
/**
|
||||
* Buttons to start and cancel image generation.
|
||||
*/
|
||||
const ProcessButtons = () => {
|
||||
return (
|
||||
<Flex layerStyle="first" sx={{ gap: 2, borderRadius: 'base', p: 2 }}>
|
||||
<InvokeButton />
|
||||
<CancelButton />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ProcessButtons);
|
@ -0,0 +1,34 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useCoreParametersCollapseLabel = () => {
|
||||
const { t } = useTranslation();
|
||||
const shouldRandomizeSeed = useAppSelector(
|
||||
(state) => state.generation.shouldRandomizeSeed
|
||||
);
|
||||
const iterations = useAppSelector((state) => state.generation.iterations);
|
||||
|
||||
const iterationsLabel = useMemo(() => {
|
||||
if (iterations === 1) {
|
||||
return t('parameters.iterationsWithCount_one', { count: 1 });
|
||||
} else {
|
||||
return t('parameters.iterationsWithCount_other', { count: iterations });
|
||||
}
|
||||
}, [iterations, t]);
|
||||
|
||||
const seedLabel = useMemo(() => {
|
||||
if (shouldRandomizeSeed) {
|
||||
return t('parameters.randomSeed');
|
||||
} else {
|
||||
return t('parameters.manualSeed');
|
||||
}
|
||||
}, [shouldRandomizeSeed, t]);
|
||||
|
||||
const iterationsAndSeedLabel = useMemo(
|
||||
() => [iterationsLabel, seedLabel].join(', '),
|
||||
[iterationsLabel, seedLabel]
|
||||
);
|
||||
|
||||
return { iterationsAndSeedLabel, iterationsLabel, seedLabel };
|
||||
};
|
@ -0,0 +1,33 @@
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import { useCancelCurrentQueueItem } from '../hooks/useCancelCurrentQueueItem';
|
||||
import QueueButton from './common/QueueButton';
|
||||
import { ChakraProps } from '@chakra-ui/react';
|
||||
|
||||
type Props = {
|
||||
asIconButton?: boolean;
|
||||
sx?: ChakraProps['sx'];
|
||||
};
|
||||
|
||||
const CancelCurrentQueueItemButton = ({ asIconButton, sx }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { cancelQueueItem, isLoading, currentQueueItemId } =
|
||||
useCancelCurrentQueueItem();
|
||||
|
||||
return (
|
||||
<QueueButton
|
||||
isDisabled={!currentQueueItemId}
|
||||
isLoading={isLoading}
|
||||
asIconButton={asIconButton}
|
||||
label={t('queue.cancel')}
|
||||
tooltip={t('queue.cancelTooltip')}
|
||||
icon={<FaTimes />}
|
||||
onClick={cancelQueueItem}
|
||||
colorScheme="error"
|
||||
sx={sx}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(CancelCurrentQueueItemButton);
|
@ -0,0 +1,32 @@
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaTrash } from 'react-icons/fa';
|
||||
import { useClearQueue } from '../hooks/useClearQueue';
|
||||
import QueueButton from './common/QueueButton';
|
||||
import { ChakraProps } from '@chakra-ui/react';
|
||||
|
||||
type Props = {
|
||||
asIconButton?: boolean;
|
||||
sx?: ChakraProps['sx'];
|
||||
};
|
||||
|
||||
const ClearQueueButton = ({ asIconButton, sx }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { clearQueue, isLoading, queueStatus } = useClearQueue();
|
||||
|
||||
return (
|
||||
<QueueButton
|
||||
isDisabled={!queueStatus?.queue.total}
|
||||
isLoading={isLoading}
|
||||
asIconButton={asIconButton}
|
||||
label={t('queue.clear')}
|
||||
tooltip={t('queue.clearTooltip')}
|
||||
icon={<FaTrash />}
|
||||
onClick={clearQueue}
|
||||
colorScheme="error"
|
||||
sx={sx}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ClearQueueButton);
|
@ -0,0 +1,18 @@
|
||||
import { memo } from 'react';
|
||||
import QueueItemCard from './common/QueueItemCard';
|
||||
import { useGetCurrentQueueItemQuery } from 'services/api/endpoints/queue';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const CurrentQueueItemCard = () => {
|
||||
const { t } = useTranslation();
|
||||
const { data: currentQueueItemData } = useGetCurrentQueueItemQuery();
|
||||
|
||||
return (
|
||||
<QueueItemCard
|
||||
label={t('queue.current')}
|
||||
session_queue_item={currentQueueItemData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(CurrentQueueItemCard);
|
@ -0,0 +1,18 @@
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetNextQueueItemQuery } from 'services/api/endpoints/queue';
|
||||
import QueueItemCard from './common/QueueItemCard';
|
||||
|
||||
const NextQueueItemCard = () => {
|
||||
const { t } = useTranslation();
|
||||
const { data: nextQueueItemData } = useGetNextQueueItemQuery();
|
||||
|
||||
return (
|
||||
<QueueItemCard
|
||||
label={t('queue.next')}
|
||||
session_queue_item={nextQueueItemData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(NextQueueItemCard);
|
@ -0,0 +1,29 @@
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaPause } from 'react-icons/fa';
|
||||
import { usePauseProcessor } from '../hooks/usePauseProcessor';
|
||||
import QueueButton from './common/QueueButton';
|
||||
|
||||
type Props = {
|
||||
asIconButton?: boolean;
|
||||
};
|
||||
|
||||
const PauseProcessorButton = ({ asIconButton }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { pauseProcessor, isLoading, isStarted } = usePauseProcessor();
|
||||
|
||||
return (
|
||||
<QueueButton
|
||||
asIconButton={asIconButton}
|
||||
label={t('queue.pause')}
|
||||
tooltip={t('queue.pauseTooltip')}
|
||||
isDisabled={!isStarted}
|
||||
isLoading={isLoading}
|
||||
icon={<FaPause />}
|
||||
onClick={pauseProcessor}
|
||||
colorScheme="gold"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(PauseProcessorButton);
|
@ -0,0 +1,29 @@
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BsStars } from 'react-icons/bs';
|
||||
import { usePruneQueue } from '../hooks/usePruneQueue';
|
||||
import QueueButton from './common/QueueButton';
|
||||
|
||||
type Props = {
|
||||
asIconButton?: boolean;
|
||||
};
|
||||
|
||||
const PruneQueueButton = ({ asIconButton }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { pruneQueue, isLoading, finishedCount } = usePruneQueue();
|
||||
|
||||
return (
|
||||
<QueueButton
|
||||
isDisabled={!finishedCount}
|
||||
isLoading={isLoading}
|
||||
asIconButton={asIconButton}
|
||||
label={t('queue.prune')}
|
||||
tooltip={t('queue.pruneTooltip', { item_count: finishedCount })}
|
||||
icon={<BsStars />}
|
||||
onClick={pruneQueue}
|
||||
colorScheme="blue"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(PruneQueueButton);
|
@ -0,0 +1,32 @@
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaPlus } from 'react-icons/fa';
|
||||
import { useQueueBack } from '../hooks/useQueueBack';
|
||||
import EnqueueButtonTooltip from './QueueButtonTooltip';
|
||||
import QueueButton from './common/QueueButton';
|
||||
import { ChakraProps } from '@chakra-ui/react';
|
||||
|
||||
type Props = {
|
||||
asIconButton?: boolean;
|
||||
sx?: ChakraProps['sx'];
|
||||
};
|
||||
|
||||
const QueueBackButton = ({ asIconButton, sx }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { queueBack, isLoading, isDisabled } = useQueueBack();
|
||||
return (
|
||||
<QueueButton
|
||||
asIconButton={asIconButton}
|
||||
colorScheme="accent"
|
||||
label={t('queue.queueBack')}
|
||||
isDisabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
onClick={queueBack}
|
||||
tooltip={<EnqueueButtonTooltip />}
|
||||
icon={<FaPlus />}
|
||||
sx={sx}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(QueueBackButton);
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user