mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): update invoke button tooltip for batching
- Split up logic to determine reason why the user cannot invoke for each tab. - Fix issue where the workflows tab would show reasons related to canvas/upscale tab. The tooltip now only shows information relevant to the current tab. - Add calculation for batch size to the queue count prediction. - Use a constant for the enqueue mutation's fixed cache key, instead of a string. Just some typo protection.
This commit is contained in:
@@ -263,7 +263,8 @@
|
|||||||
"iterations_one": "Iteration",
|
"iterations_one": "Iteration",
|
||||||
"iterations_other": "Iterations",
|
"iterations_other": "Iterations",
|
||||||
"generations_one": "Generation",
|
"generations_one": "Generation",
|
||||||
"generations_other": "Generations"
|
"generations_other": "Generations",
|
||||||
|
"batchSize": "Batch Size"
|
||||||
},
|
},
|
||||||
"invocationCache": {
|
"invocationCache": {
|
||||||
"invocationCache": "Invocation Cache",
|
"invocationCache": "Invocation Cache",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
|||||||
import { buildAdHocPostProcessingGraph } from 'features/nodes/util/graph/buildAdHocPostProcessingGraph';
|
import { buildAdHocPostProcessingGraph } from 'features/nodes/util/graph/buildAdHocPostProcessingGraph';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { enqueueMutationFixedCacheKeyOptions, queueApi } from 'services/api/endpoints/queue';
|
||||||
import type { BatchConfig, ImageDTO } from 'services/api/types';
|
import type { BatchConfig, ImageDTO } from 'services/api/types';
|
||||||
import type { JsonObject } from 'type-fest';
|
import type { JsonObject } from 'type-fest';
|
||||||
|
|
||||||
@@ -32,9 +32,7 @@ export const addAdHocPostProcessingRequestedListener = (startAppListening: AppSt
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const req = dispatch(
|
const req = dispatch(
|
||||||
queueApi.endpoints.enqueueBatch.initiate(enqueueBatchArg, {
|
queueApi.endpoints.enqueueBatch.initiate(enqueueBatchArg, enqueueMutationFixedCacheKeyOptions)
|
||||||
fixedCacheKey: 'enqueueBatch',
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const enqueueResult = await req.unwrap();
|
const enqueueResult = await req.unwrap();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { buildSDXLGraph } from 'features/nodes/util/graph/generation/buildSDXLGr
|
|||||||
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
|
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { serializeError } from 'serialize-error';
|
import { serializeError } from 'serialize-error';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { enqueueMutationFixedCacheKeyOptions, queueApi } from 'services/api/endpoints/queue';
|
||||||
import type { Invocation } from 'services/api/types';
|
import type { Invocation } from 'services/api/types';
|
||||||
import { assert, AssertionError } from 'tsafe';
|
import { assert, AssertionError } from 'tsafe';
|
||||||
import type { JsonObject } from 'type-fest';
|
import type { JsonObject } from 'type-fest';
|
||||||
@@ -91,9 +91,7 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const req = dispatch(
|
const req = dispatch(
|
||||||
queueApi.endpoints.enqueueBatch.initiate(prepareBatchResult.value, {
|
queueApi.endpoints.enqueueBatch.initiate(prepareBatchResult.value, enqueueMutationFixedCacheKeyOptions)
|
||||||
fixedCacheKey: 'enqueueBatch',
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
req.reset();
|
req.reset();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { isImageFieldCollectionInputInstance } from 'features/nodes/types/field'
|
|||||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||||
import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph';
|
import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph';
|
||||||
import { buildWorkflowWithValidation } from 'features/nodes/util/workflow/buildWorkflow';
|
import { buildWorkflowWithValidation } from 'features/nodes/util/workflow/buildWorkflow';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { enqueueMutationFixedCacheKeyOptions, queueApi } from 'services/api/endpoints/queue';
|
||||||
import type { Batch, BatchConfig } from 'services/api/types';
|
import type { Batch, BatchConfig } from 'services/api/types';
|
||||||
|
|
||||||
const log = logger('workflows');
|
const log = logger('workflows');
|
||||||
@@ -70,11 +70,7 @@ export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) =
|
|||||||
prepend: action.payload.prepend,
|
prepend: action.payload.prepend,
|
||||||
};
|
};
|
||||||
|
|
||||||
const req = dispatch(
|
const req = dispatch(queueApi.endpoints.enqueueBatch.initiate(batchConfig, enqueueMutationFixedCacheKeyOptions));
|
||||||
queueApi.endpoints.enqueueBatch.initiate(batchConfig, {
|
|
||||||
fixedCacheKey: 'enqueueBatch',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
await req.unwrap();
|
await req.unwrap();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { enqueueRequested } from 'app/store/actions';
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
||||||
import { buildMultidiffusionUpscaleGraph } from 'features/nodes/util/graph/buildMultidiffusionUpscaleGraph';
|
import { buildMultidiffusionUpscaleGraph } from 'features/nodes/util/graph/buildMultidiffusionUpscaleGraph';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { enqueueMutationFixedCacheKeyOptions, queueApi } from 'services/api/endpoints/queue';
|
||||||
|
|
||||||
export const addEnqueueRequestedUpscale = (startAppListening: AppStartListening) => {
|
export const addEnqueueRequestedUpscale = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
@@ -16,11 +16,7 @@ export const addEnqueueRequestedUpscale = (startAppListening: AppStartListening)
|
|||||||
|
|
||||||
const batchConfig = prepareLinearUIBatch(state, g, prepend, noise, posCond, 'upscaling', 'gallery');
|
const batchConfig = prepareLinearUIBatch(state, g, prepend, noise, posCond, 'upscaling', 'gallery');
|
||||||
|
|
||||||
const req = dispatch(
|
const req = dispatch(queueApi.endpoints.enqueueBatch.initiate(batchConfig, enqueueMutationFixedCacheKeyOptions));
|
||||||
queueApi.endpoints.enqueueBatch.initiate(batchConfig, {
|
|
||||||
fixedCacheKey: 'enqueueBatch',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
await req.unwrap();
|
await req.unwrap();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import type { Graph } from 'features/nodes/util/graph/generation/Graph';
|
|||||||
import { atom, computed } from 'nanostores';
|
import { atom, computed } from 'nanostores';
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
import { getImageDTO } from 'services/api/endpoints/images';
|
import { getImageDTO } from 'services/api/endpoints/images';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { enqueueMutationFixedCacheKeyOptions, queueApi } from 'services/api/endpoints/queue';
|
||||||
import type { BatchConfig, ImageDTO, S } from 'services/api/types';
|
import type { BatchConfig, ImageDTO, S } from 'services/api/types';
|
||||||
import { QueueError } from 'services/events/errors';
|
import { QueueError } from 'services/events/errors';
|
||||||
import type { Param0 } from 'tsafe';
|
import type { Param0 } from 'tsafe';
|
||||||
@@ -402,7 +402,7 @@ export class CanvasStateApiModule extends CanvasModuleBase {
|
|||||||
queueApi.endpoints.enqueueBatch.initiate(batch, {
|
queueApi.endpoints.enqueueBatch.initiate(batch, {
|
||||||
// Use the same cache key for all enqueueBatch requests, so that all consumers of this query get the same status
|
// Use the same cache key for all enqueueBatch requests, so that all consumers of this query get the same status
|
||||||
// updates.
|
// updates.
|
||||||
fixedCacheKey: 'enqueueBatch',
|
...enqueueMutationFixedCacheKeyOptions,
|
||||||
// We do not need RTK to track this request in the store
|
// We do not need RTK to track this request in the store
|
||||||
track: false,
|
track: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,310 @@
|
|||||||
|
import type { TooltipProps } from '@invoke-ai/ui-library';
|
||||||
|
import { Divider, Flex, ListItem, Text, Tooltip, UnorderedList } from '@invoke-ai/ui-library';
|
||||||
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { $true } from 'app/store/nanostores/util';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { useCanvasManagerSafe } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
|
import { selectSendToCanvas } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||||
|
import { selectIterations } from 'features/controlLayers/store/paramsSlice';
|
||||||
|
import { selectDynamicPromptsIsLoading } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||||
|
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
||||||
|
import { $templates } from 'features/nodes/store/nodesSlice';
|
||||||
|
import type { Reason } from 'features/queue/store/readiness';
|
||||||
|
import {
|
||||||
|
buildSelectIsReadyToEnqueueCanvasTab,
|
||||||
|
buildSelectIsReadyToEnqueueUpscaleTab,
|
||||||
|
buildSelectIsReadyToEnqueueWorkflowsTab,
|
||||||
|
buildSelectReasonsWhyCannotEnqueueCanvasTab,
|
||||||
|
buildSelectReasonsWhyCannotEnqueueUpscaleTab,
|
||||||
|
buildSelectReasonsWhyCannotEnqueueWorkflowsTab,
|
||||||
|
selectPromptsCount,
|
||||||
|
selectWorkflowsBatchSize,
|
||||||
|
} from 'features/queue/store/readiness';
|
||||||
|
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||||
|
import type { PropsWithChildren } from 'react';
|
||||||
|
import { memo, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { enqueueMutationFixedCacheKeyOptions, useEnqueueBatchMutation } from 'services/api/endpoints/queue';
|
||||||
|
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||||
|
import { $isConnected } from 'services/events/stores';
|
||||||
|
|
||||||
|
type Props = TooltipProps & {
|
||||||
|
prepend?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InvokeButtonTooltip = ({ prepend, children, ...rest }: PropsWithChildren<Props>) => {
|
||||||
|
return (
|
||||||
|
<Tooltip label={<TooltipContent prepend={prepend} />} maxW={512} {...rest}>
|
||||||
|
{children}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TooltipContent = memo(({ prepend = false }: { prepend?: boolean }) => {
|
||||||
|
const activeTab = useAppSelector(selectActiveTab);
|
||||||
|
|
||||||
|
if (activeTab === 'canvas') {
|
||||||
|
return <CanvasTabTooltipContent prepend={prepend} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeTab === 'workflows') {
|
||||||
|
return <WorkflowsTabTooltipContent prepend={prepend} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeTab === 'upscaling') {
|
||||||
|
return <UpscaleTabTooltipContent prepend={prepend} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
TooltipContent.displayName = 'TooltipContent';
|
||||||
|
|
||||||
|
const CanvasTabTooltipContent = memo(({ prepend = false }: { prepend?: boolean }) => {
|
||||||
|
const isConnected = useStore($isConnected);
|
||||||
|
const canvasManager = useCanvasManagerSafe();
|
||||||
|
const canvasIsFiltering = useStore(canvasManager?.stateApi.$isFiltering ?? $true);
|
||||||
|
const canvasIsTransforming = useStore(canvasManager?.stateApi.$isTransforming ?? $true);
|
||||||
|
const canvasIsRasterizing = useStore(canvasManager?.stateApi.$isRasterizing ?? $true);
|
||||||
|
const canvasIsSelectingObject = useStore(canvasManager?.stateApi.$isSegmenting ?? $true);
|
||||||
|
const canvasIsCompositing = useStore(canvasManager?.compositor.$isBusy ?? $true);
|
||||||
|
|
||||||
|
const selectIsReady = useMemo(
|
||||||
|
() =>
|
||||||
|
buildSelectIsReadyToEnqueueCanvasTab({
|
||||||
|
isConnected,
|
||||||
|
canvasIsFiltering,
|
||||||
|
canvasIsTransforming,
|
||||||
|
canvasIsRasterizing,
|
||||||
|
canvasIsSelectingObject,
|
||||||
|
canvasIsCompositing,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
isConnected,
|
||||||
|
canvasIsCompositing,
|
||||||
|
canvasIsFiltering,
|
||||||
|
canvasIsRasterizing,
|
||||||
|
canvasIsSelectingObject,
|
||||||
|
canvasIsTransforming,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectReasons = useMemo(
|
||||||
|
() =>
|
||||||
|
buildSelectReasonsWhyCannotEnqueueCanvasTab({
|
||||||
|
isConnected,
|
||||||
|
canvasIsFiltering,
|
||||||
|
canvasIsTransforming,
|
||||||
|
canvasIsRasterizing,
|
||||||
|
canvasIsSelectingObject,
|
||||||
|
canvasIsCompositing,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
isConnected,
|
||||||
|
canvasIsCompositing,
|
||||||
|
canvasIsFiltering,
|
||||||
|
canvasIsRasterizing,
|
||||||
|
canvasIsSelectingObject,
|
||||||
|
canvasIsTransforming,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const isReady = useAppSelector(selectIsReady);
|
||||||
|
const reasons = useAppSelector(selectReasons);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDir="column" gap={1}>
|
||||||
|
<IsReadyText isReady={isReady} prepend={prepend} />
|
||||||
|
<QueueCountPredictionCanvasOrUpscaleTab />
|
||||||
|
{reasons.length > 0 && (
|
||||||
|
<>
|
||||||
|
<StyledDivider />
|
||||||
|
<ReasonsList reasons={reasons} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<StyledDivider />
|
||||||
|
<AddingToText />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
CanvasTabTooltipContent.displayName = 'CanvasTabTooltipContent';
|
||||||
|
|
||||||
|
const UpscaleTabTooltipContent = memo(({ prepend = false }: { prepend?: boolean }) => {
|
||||||
|
const isConnected = useStore($isConnected);
|
||||||
|
|
||||||
|
const selectIsReady = useMemo(() => buildSelectIsReadyToEnqueueUpscaleTab({ isConnected }), [isConnected]);
|
||||||
|
const selectReasons = useMemo(() => buildSelectReasonsWhyCannotEnqueueUpscaleTab({ isConnected }), [isConnected]);
|
||||||
|
|
||||||
|
const isReady = useAppSelector(selectIsReady);
|
||||||
|
const reasons = useAppSelector(selectReasons);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDir="column" gap={1}>
|
||||||
|
<IsReadyText isReady={isReady} prepend={prepend} />
|
||||||
|
<QueueCountPredictionCanvasOrUpscaleTab />
|
||||||
|
{reasons.length > 0 && (
|
||||||
|
<>
|
||||||
|
<StyledDivider />
|
||||||
|
<ReasonsList reasons={reasons} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<StyledDivider />
|
||||||
|
<AddingToText />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
UpscaleTabTooltipContent.displayName = 'UpscaleTabTooltipContent';
|
||||||
|
|
||||||
|
const WorkflowsTabTooltipContent = memo(({ prepend = false }: { prepend?: boolean }) => {
|
||||||
|
const isConnected = useStore($isConnected);
|
||||||
|
const templates = useStore($templates);
|
||||||
|
|
||||||
|
const selectIsReady = useMemo(
|
||||||
|
() => buildSelectIsReadyToEnqueueWorkflowsTab({ isConnected, templates }),
|
||||||
|
[isConnected, templates]
|
||||||
|
);
|
||||||
|
const selectReasons = useMemo(
|
||||||
|
() => buildSelectReasonsWhyCannotEnqueueWorkflowsTab({ isConnected, templates }),
|
||||||
|
[isConnected, templates]
|
||||||
|
);
|
||||||
|
|
||||||
|
const isReady = useAppSelector(selectIsReady);
|
||||||
|
const reasons = useAppSelector(selectReasons);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDir="column" gap={1}>
|
||||||
|
<IsReadyText isReady={isReady} prepend={prepend} />
|
||||||
|
<QueueCountPredictionWorkflowsTab />
|
||||||
|
{reasons.length > 0 && (
|
||||||
|
<>
|
||||||
|
<StyledDivider />
|
||||||
|
<ReasonsList reasons={reasons} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<StyledDivider />
|
||||||
|
<AddingToText />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
WorkflowsTabTooltipContent.displayName = 'WorkflowsTabTooltipContent';
|
||||||
|
|
||||||
|
const QueueCountPredictionCanvasOrUpscaleTab = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const promptsCount = useAppSelector(selectPromptsCount);
|
||||||
|
const iterationsCount = useAppSelector(selectIterations);
|
||||||
|
|
||||||
|
const text = useMemo(() => {
|
||||||
|
const generationCount = Math.min(promptsCount * iterationsCount, 10000);
|
||||||
|
const prompts = t('queue.prompts', { count: promptsCount });
|
||||||
|
const iterations = t('queue.iterations', { count: iterationsCount });
|
||||||
|
const generations = t('queue.generations', { count: generationCount });
|
||||||
|
return `${promptsCount} ${prompts} \u00d7 ${iterationsCount} ${iterations} -> ${generationCount} ${generations}`.toLowerCase();
|
||||||
|
}, [iterationsCount, promptsCount, t]);
|
||||||
|
|
||||||
|
return <Text>{text}</Text>;
|
||||||
|
});
|
||||||
|
QueueCountPredictionCanvasOrUpscaleTab.displayName = 'QueueCountPredictionCanvasOrUpscaleTab';
|
||||||
|
|
||||||
|
const QueueCountPredictionWorkflowsTab = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const batchSize = useAppSelector(selectWorkflowsBatchSize);
|
||||||
|
const iterationsCount = useAppSelector(selectIterations);
|
||||||
|
|
||||||
|
const text = useMemo(() => {
|
||||||
|
const generationCount = Math.min(batchSize * iterationsCount, 10000);
|
||||||
|
const iterations = t('queue.iterations', { count: iterationsCount });
|
||||||
|
const generations = t('queue.generations', { count: generationCount });
|
||||||
|
return `${batchSize} ${t('queue.batchSize')} \u00d7 ${iterationsCount} ${iterations} -> ${generationCount} ${generations}`.toLowerCase();
|
||||||
|
}, [batchSize, iterationsCount, t]);
|
||||||
|
|
||||||
|
return <Text>{text}</Text>;
|
||||||
|
});
|
||||||
|
QueueCountPredictionWorkflowsTab.displayName = 'QueueCountPredictionWorkflowsTab';
|
||||||
|
|
||||||
|
const IsReadyText = memo(({ isReady, prepend }: { isReady: boolean; prepend: boolean }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isLoadingDynamicPrompts = useAppSelector(selectDynamicPromptsIsLoading);
|
||||||
|
const [_, enqueueMutation] = useEnqueueBatchMutation(enqueueMutationFixedCacheKeyOptions);
|
||||||
|
|
||||||
|
const text = useMemo(() => {
|
||||||
|
if (enqueueMutation.isLoading) {
|
||||||
|
return t('queue.enqueueing');
|
||||||
|
}
|
||||||
|
if (isLoadingDynamicPrompts) {
|
||||||
|
return t('dynamicPrompts.loading');
|
||||||
|
}
|
||||||
|
if (isReady) {
|
||||||
|
if (prepend) {
|
||||||
|
return t('queue.queueFront');
|
||||||
|
}
|
||||||
|
return t('queue.queueBack');
|
||||||
|
}
|
||||||
|
return t('queue.notReady');
|
||||||
|
}, [enqueueMutation.isLoading, isLoadingDynamicPrompts, isReady, prepend, t]);
|
||||||
|
|
||||||
|
return <Text fontWeight="semibold">{text}</Text>;
|
||||||
|
});
|
||||||
|
IsReadyText.displayName = 'IsReadyText';
|
||||||
|
|
||||||
|
const ReasonsList = memo(({ reasons }: { reasons: Reason[] }) => {
|
||||||
|
return (
|
||||||
|
<UnorderedList>
|
||||||
|
{reasons.map((reason, i) => (
|
||||||
|
<ReasonListItem key={`${reason.content}.${i}`} reason={reason} />
|
||||||
|
))}
|
||||||
|
</UnorderedList>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
ReasonsList.displayName = 'ReasonsList';
|
||||||
|
|
||||||
|
const ReasonListItem = memo(({ reason }: { reason: Reason }) => {
|
||||||
|
return (
|
||||||
|
<ListItem>
|
||||||
|
<span>
|
||||||
|
{reason.prefix && (
|
||||||
|
<Text as="span" fontWeight="semibold">
|
||||||
|
{reason.prefix}:{' '}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Text as="span">{reason.content}</Text>
|
||||||
|
</span>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
ReasonListItem.displayName = 'ReasonListItem';
|
||||||
|
|
||||||
|
const StyledDivider = memo(() => <Divider opacity={0.2} borderColor="base.900" />);
|
||||||
|
StyledDivider.displayName = 'StyledDivider';
|
||||||
|
|
||||||
|
const AddingToText = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const sendToCanvas = useAppSelector(selectSendToCanvas);
|
||||||
|
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
||||||
|
const autoAddBoardName = useBoardName(autoAddBoardId);
|
||||||
|
|
||||||
|
const addingTo = useMemo(() => {
|
||||||
|
if (sendToCanvas) {
|
||||||
|
return t('controlLayers.stagingOnCanvas');
|
||||||
|
}
|
||||||
|
return t('parameters.invoke.addingImagesTo');
|
||||||
|
}, [sendToCanvas, t]);
|
||||||
|
|
||||||
|
const destination = useMemo(() => {
|
||||||
|
if (sendToCanvas) {
|
||||||
|
return t('queue.canvas');
|
||||||
|
}
|
||||||
|
if (autoAddBoardName) {
|
||||||
|
return autoAddBoardName;
|
||||||
|
}
|
||||||
|
return t('boards.uncategorized');
|
||||||
|
}, [autoAddBoardName, sendToCanvas, t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text fontStyle="oblique 10deg">
|
||||||
|
{addingTo}{' '}
|
||||||
|
<Text as="span" fontWeight="semibold">
|
||||||
|
{destination}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
AddingToText.displayName = 'AddingToText';
|
||||||
@@ -6,7 +6,7 @@ import { useInvoke } from 'features/queue/hooks/useInvoke';
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { PiLightningFill, PiSparkleFill } from 'react-icons/pi';
|
import { PiLightningFill, PiSparkleFill } from 'react-icons/pi';
|
||||||
|
|
||||||
import { QueueButtonTooltip } from './QueueButtonTooltip';
|
import { InvokeButtonTooltip } from './InvokeButtonTooltip/InvokeButtonTooltip';
|
||||||
|
|
||||||
const invoke = 'Invoke';
|
const invoke = 'Invoke';
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ export const InvokeButton = memo(() => {
|
|||||||
return (
|
return (
|
||||||
<Flex pos="relative" w="200px">
|
<Flex pos="relative" w="200px">
|
||||||
<QueueIterationsNumberInput />
|
<QueueIterationsNumberInput />
|
||||||
<QueueButtonTooltip prepend={shift}>
|
<InvokeButtonTooltip prepend={shift}>
|
||||||
<Button
|
<Button
|
||||||
onClick={shift ? queue.queueFront : queue.queueBack}
|
onClick={shift ? queue.queueFront : queue.queueBack}
|
||||||
isLoading={queue.isLoading || isLoadingDynamicPrompts}
|
isLoading={queue.isLoading || isLoadingDynamicPrompts}
|
||||||
@@ -36,7 +36,7 @@ export const InvokeButton = memo(() => {
|
|||||||
<span>{invoke}</span>
|
<span>{invoke}</span>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
</Button>
|
</Button>
|
||||||
</QueueButtonTooltip>
|
</InvokeButtonTooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
import type { TooltipProps } from '@invoke-ai/ui-library';
|
|
||||||
import { Divider, Flex, ListItem, Text, Tooltip, UnorderedList } from '@invoke-ai/ui-library';
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue';
|
|
||||||
import { selectSendToCanvas } from 'features/controlLayers/store/canvasSettingsSlice';
|
|
||||||
import { selectIterations, selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
|
|
||||||
import {
|
|
||||||
selectDynamicPromptsIsLoading,
|
|
||||||
selectDynamicPromptsSlice,
|
|
||||||
} from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
|
||||||
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
|
|
||||||
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
|
||||||
import type { PropsWithChildren } from 'react';
|
|
||||||
import { memo, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useEnqueueBatchMutation } from 'services/api/endpoints/queue';
|
|
||||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
|
||||||
|
|
||||||
const selectPromptsCount = createSelector(selectParamsSlice, selectDynamicPromptsSlice, (params, dynamicPrompts) =>
|
|
||||||
getShouldProcessPrompt(params.positivePrompt) ? dynamicPrompts.prompts.length : 1
|
|
||||||
);
|
|
||||||
|
|
||||||
type Props = TooltipProps & {
|
|
||||||
prepend?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const QueueButtonTooltip = ({ prepend, children, ...rest }: PropsWithChildren<Props>) => {
|
|
||||||
return (
|
|
||||||
<Tooltip label={<TooltipContent prepend={prepend} />} maxW={512} {...rest}>
|
|
||||||
{children}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TooltipContent = memo(({ prepend = false }: { prepend?: boolean }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { isReady, reasons } = useIsReadyToEnqueue();
|
|
||||||
const sendToCanvas = useAppSelector(selectSendToCanvas);
|
|
||||||
const isLoadingDynamicPrompts = useAppSelector(selectDynamicPromptsIsLoading);
|
|
||||||
const promptsCount = useAppSelector(selectPromptsCount);
|
|
||||||
const iterationsCount = useAppSelector(selectIterations);
|
|
||||||
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
|
||||||
const autoAddBoardName = useBoardName(autoAddBoardId);
|
|
||||||
const [_, { isLoading }] = useEnqueueBatchMutation({
|
|
||||||
fixedCacheKey: 'enqueueBatch',
|
|
||||||
});
|
|
||||||
const queueCountPredictionLabel = useMemo(() => {
|
|
||||||
const generationCount = Math.min(promptsCount * iterationsCount, 10000);
|
|
||||||
const prompts = t('queue.prompts', { count: promptsCount });
|
|
||||||
const iterations = t('queue.iterations', { count: iterationsCount });
|
|
||||||
const generations = t('queue.generations', { count: generationCount });
|
|
||||||
return `${promptsCount} ${prompts} \u00d7 ${iterationsCount} ${iterations} -> ${generationCount} ${generations}`.toLowerCase();
|
|
||||||
}, [iterationsCount, promptsCount, t]);
|
|
||||||
|
|
||||||
const label = useMemo(() => {
|
|
||||||
if (isLoading) {
|
|
||||||
return t('queue.enqueueing');
|
|
||||||
}
|
|
||||||
if (isLoadingDynamicPrompts) {
|
|
||||||
return t('dynamicPrompts.loading');
|
|
||||||
}
|
|
||||||
if (isReady) {
|
|
||||||
if (prepend) {
|
|
||||||
return t('queue.queueFront');
|
|
||||||
}
|
|
||||||
return t('queue.queueBack');
|
|
||||||
}
|
|
||||||
return t('queue.notReady');
|
|
||||||
}, [isLoading, isLoadingDynamicPrompts, isReady, prepend, t]);
|
|
||||||
|
|
||||||
const addingTo = useMemo(() => {
|
|
||||||
if (sendToCanvas) {
|
|
||||||
return t('controlLayers.stagingOnCanvas');
|
|
||||||
}
|
|
||||||
return t('parameters.invoke.addingImagesTo');
|
|
||||||
}, [sendToCanvas, t]);
|
|
||||||
|
|
||||||
const destination = useMemo(() => {
|
|
||||||
if (sendToCanvas) {
|
|
||||||
return t('queue.canvas');
|
|
||||||
}
|
|
||||||
if (autoAddBoardName) {
|
|
||||||
return autoAddBoardName;
|
|
||||||
}
|
|
||||||
return t('boards.uncategorized');
|
|
||||||
}, [autoAddBoardName, sendToCanvas, t]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex flexDir="column" gap={1}>
|
|
||||||
<Text fontWeight="semibold">{label}</Text>
|
|
||||||
<Text>{queueCountPredictionLabel}</Text>
|
|
||||||
{reasons.length > 0 && (
|
|
||||||
<>
|
|
||||||
<Divider opacity={0.2} borderColor="base.900" />
|
|
||||||
<UnorderedList>
|
|
||||||
{reasons.map((reason, i) => (
|
|
||||||
<ListItem key={`${reason.content}.${i}`}>
|
|
||||||
<span>
|
|
||||||
{reason.prefix && (
|
|
||||||
<Text as="span" fontWeight="semibold">
|
|
||||||
{reason.prefix}:{' '}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
<Text as="span">{reason.content}</Text>
|
|
||||||
</span>
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
</UnorderedList>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Divider opacity={0.2} borderColor="base.900" />
|
|
||||||
<Text fontStyle="oblique 10deg">
|
|
||||||
{addingTo}{' '}
|
|
||||||
<Text as="span" fontWeight="semibold">
|
|
||||||
{destination}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
TooltipContent.displayName = 'QueueButtonTooltipContent';
|
|
||||||
@@ -1,17 +1,63 @@
|
|||||||
|
import { useStore } from '@nanostores/react';
|
||||||
import { enqueueRequested } from 'app/store/actions';
|
import { enqueueRequested } from 'app/store/actions';
|
||||||
|
import { $true } from 'app/store/nanostores/util';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue';
|
import { useCanvasManagerSafe } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
|
import { $templates } from 'features/nodes/store/nodesSlice';
|
||||||
|
import {
|
||||||
|
buildSelectIsReadyToEnqueueCanvasTab,
|
||||||
|
buildSelectIsReadyToEnqueueUpscaleTab,
|
||||||
|
buildSelectIsReadyToEnqueueWorkflowsTab,
|
||||||
|
} from 'features/queue/store/readiness';
|
||||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useEnqueueBatchMutation } from 'services/api/endpoints/queue';
|
import { enqueueMutationFixedCacheKeyOptions, useEnqueueBatchMutation } from 'services/api/endpoints/queue';
|
||||||
|
import { $isConnected } from 'services/events/stores';
|
||||||
|
|
||||||
export const useInvoke = () => {
|
export const useInvoke = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const tabName = useAppSelector(selectActiveTab);
|
const tabName = useAppSelector(selectActiveTab);
|
||||||
const { isReady } = useIsReadyToEnqueue();
|
const isConnected = useStore($isConnected);
|
||||||
const [_, { isLoading }] = useEnqueueBatchMutation({
|
const canvasManager = useCanvasManagerSafe();
|
||||||
fixedCacheKey: 'enqueueBatch',
|
const canvasIsFiltering = useStore(canvasManager?.stateApi.$isFiltering ?? $true);
|
||||||
});
|
const canvasIsTransforming = useStore(canvasManager?.stateApi.$isTransforming ?? $true);
|
||||||
|
const canvasIsRasterizing = useStore(canvasManager?.stateApi.$isRasterizing ?? $true);
|
||||||
|
const canvasIsSelectingObject = useStore(canvasManager?.stateApi.$isSegmenting ?? $true);
|
||||||
|
const canvasIsCompositing = useStore(canvasManager?.compositor.$isBusy ?? $true);
|
||||||
|
const templates = useStore($templates);
|
||||||
|
|
||||||
|
const selectIsReady = useMemo(() => {
|
||||||
|
if (tabName === 'canvas') {
|
||||||
|
return buildSelectIsReadyToEnqueueCanvasTab({
|
||||||
|
isConnected,
|
||||||
|
canvasIsFiltering,
|
||||||
|
canvasIsTransforming,
|
||||||
|
canvasIsRasterizing,
|
||||||
|
canvasIsSelectingObject,
|
||||||
|
canvasIsCompositing,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (tabName === 'upscaling') {
|
||||||
|
return buildSelectIsReadyToEnqueueUpscaleTab({ isConnected });
|
||||||
|
}
|
||||||
|
if (tabName === 'workflows') {
|
||||||
|
return buildSelectIsReadyToEnqueueWorkflowsTab({ isConnected, templates });
|
||||||
|
}
|
||||||
|
return () => false;
|
||||||
|
}, [
|
||||||
|
tabName,
|
||||||
|
isConnected,
|
||||||
|
canvasIsFiltering,
|
||||||
|
canvasIsTransforming,
|
||||||
|
canvasIsRasterizing,
|
||||||
|
canvasIsSelectingObject,
|
||||||
|
canvasIsCompositing,
|
||||||
|
templates,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const isReady = useAppSelector(selectIsReady);
|
||||||
|
|
||||||
|
const [_, { isLoading }] = useEnqueueBatchMutation(enqueueMutationFixedCacheKeyOptions);
|
||||||
const queueBack = useCallback(() => {
|
const queueBack = useCallback(() => {
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
enqueueMutationFixedCacheKeyOptions,
|
||||||
useCancelQueueItemMutation,
|
useCancelQueueItemMutation,
|
||||||
// useCancelByBatchIdsMutation,
|
// useCancelByBatchIdsMutation,
|
||||||
useClearQueueMutation,
|
useClearQueueMutation,
|
||||||
@@ -9,9 +10,9 @@ import {
|
|||||||
} from 'services/api/endpoints/queue';
|
} from 'services/api/endpoints/queue';
|
||||||
|
|
||||||
export const useIsQueueMutationInProgress = () => {
|
export const useIsQueueMutationInProgress = () => {
|
||||||
const [_triggerEnqueueBatch, { isLoading: isLoadingEnqueueBatch }] = useEnqueueBatchMutation({
|
const [_triggerEnqueueBatch, { isLoading: isLoadingEnqueueBatch }] = useEnqueueBatchMutation(
|
||||||
fixedCacheKey: 'enqueueBatch',
|
enqueueMutationFixedCacheKeyOptions
|
||||||
});
|
);
|
||||||
const [_triggerResumeProcessor, { isLoading: isLoadingResumeProcessor }] = useResumeProcessorMutation({
|
const [_triggerResumeProcessor, { isLoading: isLoadingResumeProcessor }] = useResumeProcessorMutation({
|
||||||
fixedCacheKey: 'resumeProcessor',
|
fixedCacheKey: 'resumeProcessor',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { useStore } from '@nanostores/react';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|
||||||
import { $true } from 'app/store/nanostores/util';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import type { AppConfig } from 'app/types/invokeai';
|
import type { AppConfig } from 'app/types/invokeai';
|
||||||
import { useCanvasManagerSafe } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
|
||||||
import type { ParamsState } from 'features/controlLayers/store/paramsSlice';
|
import type { ParamsState } from 'features/controlLayers/store/paramsSlice';
|
||||||
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
|
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
|
||||||
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||||
@@ -11,7 +7,6 @@ import type { CanvasState } from 'features/controlLayers/store/types';
|
|||||||
import type { DynamicPromptsState } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
import type { DynamicPromptsState } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||||
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||||
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
|
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
|
||||||
import { $templates } from 'features/nodes/store/nodesSlice';
|
|
||||||
import { selectNodesSlice } from 'features/nodes/store/selectors';
|
import { selectNodesSlice } from 'features/nodes/store/selectors';
|
||||||
import type { NodesState, Templates } from 'features/nodes/store/types';
|
import type { NodesState, Templates } from 'features/nodes/store/types';
|
||||||
import type { WorkflowSettingsState } from 'features/nodes/store/workflowSettingsSlice';
|
import type { WorkflowSettingsState } from 'features/nodes/store/workflowSettingsSlice';
|
||||||
@@ -21,13 +16,17 @@ import { isInvocationNode } from 'features/nodes/types/invocation';
|
|||||||
import type { UpscaleState } from 'features/parameters/store/upscaleSlice';
|
import type { UpscaleState } from 'features/parameters/store/upscaleSlice';
|
||||||
import { selectUpscaleSlice } from 'features/parameters/store/upscaleSlice';
|
import { selectUpscaleSlice } from 'features/parameters/store/upscaleSlice';
|
||||||
import { selectConfigSlice } from 'features/system/store/configSlice';
|
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||||
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
|
||||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import { forEach, upperFirst } from 'lodash-es';
|
import { forEach, upperFirst } from 'lodash-es';
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { getConnectedEdges } from 'reactflow';
|
import { getConnectedEdges } from 'reactflow';
|
||||||
import { $isConnected } from 'services/events/stores';
|
|
||||||
|
/**
|
||||||
|
* This file contains selectors and utilities for determining the app is ready to enqueue generations. The handling
|
||||||
|
* differs for each tab (canvas, upscaling, workflows).
|
||||||
|
*
|
||||||
|
* For example, the canvas tab needs to check the status of the canvas manager before enqueuing, while the workflows
|
||||||
|
* tab needs to check the status of the nodes and their connections.
|
||||||
|
*/
|
||||||
|
|
||||||
const LAYER_TYPE_TO_TKEY = {
|
const LAYER_TYPE_TO_TKEY = {
|
||||||
reference_image: 'controlLayers.referenceImage',
|
reference_image: 'controlLayers.referenceImage',
|
||||||
@@ -37,15 +36,22 @@ const LAYER_TYPE_TO_TKEY = {
|
|||||||
control_layer: 'controlLayers.controlLayer',
|
control_layer: 'controlLayers.controlLayer',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type Reason = { prefix?: string; content: string };
|
export type Reason = { prefix?: string; content: string };
|
||||||
|
|
||||||
const handleWorkflowsTab = (arg: {
|
const disconnectedReason = (t: typeof i18n.t) => ({ content: t('parameters.invoke.systemDisconnected') });
|
||||||
reasons: Reason[];
|
|
||||||
|
const getReasonsWhyCannotEnqueueWorkflowsTab = (arg: {
|
||||||
|
isConnected: boolean;
|
||||||
nodes: NodesState;
|
nodes: NodesState;
|
||||||
workflowSettings: WorkflowSettingsState;
|
workflowSettings: WorkflowSettingsState;
|
||||||
templates: Templates;
|
templates: Templates;
|
||||||
}) => {
|
}): Reason[] => {
|
||||||
const { reasons, nodes, workflowSettings, templates } = arg;
|
const { isConnected, nodes, workflowSettings, templates } = arg;
|
||||||
|
const reasons: Reason[] = [];
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
reasons.push(disconnectedReason(i18n.t));
|
||||||
|
}
|
||||||
|
|
||||||
if (workflowSettings.shouldValidateGraph) {
|
if (workflowSettings.shouldValidateGraph) {
|
||||||
if (!nodes.nodes.length) {
|
if (!nodes.nodes.length) {
|
||||||
@@ -121,15 +127,22 @@ const handleWorkflowsTab = (arg: {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return reasons;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpscalingTab = (arg: {
|
const getReasonsWhyCannotEnqueueUpscaleTab = (arg: {
|
||||||
reasons: Reason[];
|
isConnected: boolean;
|
||||||
upscale: UpscaleState;
|
upscale: UpscaleState;
|
||||||
config: AppConfig;
|
config: AppConfig;
|
||||||
params: ParamsState;
|
params: ParamsState;
|
||||||
}) => {
|
}) => {
|
||||||
const { reasons, upscale, config, params } = arg;
|
const { isConnected, upscale, config, params } = arg;
|
||||||
|
const reasons: Reason[] = [];
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
reasons.push(disconnectedReason(i18n.t));
|
||||||
|
}
|
||||||
|
|
||||||
if (!upscale.upscaleInitialImage) {
|
if (!upscale.upscaleInitialImage) {
|
||||||
reasons.push({ content: i18n.t('upscaling.missingUpscaleInitialImage') });
|
reasons.push({ content: i18n.t('upscaling.missingUpscaleInitialImage') });
|
||||||
@@ -160,10 +173,12 @@ const handleUpscalingTab = (arg: {
|
|||||||
reasons.push({ content: i18n.t('upscaling.missingTileControlNetModel') });
|
reasons.push({ content: i18n.t('upscaling.missingTileControlNetModel') });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return reasons;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCanvasTab = (arg: {
|
const getReasonsWhyCannotEnqueueCanvasTab = (arg: {
|
||||||
reasons: Reason[];
|
isConnected: boolean;
|
||||||
canvas: CanvasState;
|
canvas: CanvasState;
|
||||||
params: ParamsState;
|
params: ParamsState;
|
||||||
dynamicPrompts: DynamicPromptsState;
|
dynamicPrompts: DynamicPromptsState;
|
||||||
@@ -174,7 +189,7 @@ const handleCanvasTab = (arg: {
|
|||||||
canvasIsSelectingObject: boolean;
|
canvasIsSelectingObject: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
reasons,
|
isConnected,
|
||||||
canvas,
|
canvas,
|
||||||
params,
|
params,
|
||||||
dynamicPrompts,
|
dynamicPrompts,
|
||||||
@@ -184,8 +199,12 @@ const handleCanvasTab = (arg: {
|
|||||||
canvasIsCompositing,
|
canvasIsCompositing,
|
||||||
canvasIsSelectingObject,
|
canvasIsSelectingObject,
|
||||||
} = arg;
|
} = arg;
|
||||||
|
|
||||||
const { model, positivePrompt } = params;
|
const { model, positivePrompt } = params;
|
||||||
|
const reasons: Reason[] = [];
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
reasons.push(disconnectedReason(i18n.t));
|
||||||
|
}
|
||||||
|
|
||||||
if (canvasIsFiltering) {
|
if (canvasIsFiltering) {
|
||||||
reasons.push({ content: i18n.t('parameters.invoke.canvasIsFiltering') });
|
reasons.push({ content: i18n.t('parameters.invoke.canvasIsFiltering') });
|
||||||
@@ -353,10 +372,11 @@ const handleCanvasTab = (arg: {
|
|||||||
reasons.push({ prefix, content });
|
reasons.push({ prefix, content });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return reasons;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createSelector = (arg: {
|
export const buildSelectReasonsWhyCannotEnqueueCanvasTab = (arg: {
|
||||||
templates: Templates;
|
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
canvasIsFiltering: boolean;
|
canvasIsFiltering: boolean;
|
||||||
canvasIsTransforming: boolean;
|
canvasIsTransforming: boolean;
|
||||||
@@ -365,7 +385,6 @@ const createSelector = (arg: {
|
|||||||
canvasIsSelectingObject: boolean;
|
canvasIsSelectingObject: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
templates,
|
|
||||||
isConnected,
|
isConnected,
|
||||||
canvasIsFiltering,
|
canvasIsFiltering,
|
||||||
canvasIsTransforming,
|
canvasIsTransforming,
|
||||||
@@ -373,79 +392,127 @@ const createSelector = (arg: {
|
|||||||
canvasIsCompositing,
|
canvasIsCompositing,
|
||||||
canvasIsSelectingObject,
|
canvasIsSelectingObject,
|
||||||
} = arg;
|
} = arg;
|
||||||
return createMemoizedSelector(
|
|
||||||
[
|
|
||||||
selectSystemSlice,
|
|
||||||
selectNodesSlice,
|
|
||||||
selectWorkflowSettingsSlice,
|
|
||||||
selectDynamicPromptsSlice,
|
|
||||||
selectCanvasSlice,
|
|
||||||
selectParamsSlice,
|
|
||||||
selectUpscaleSlice,
|
|
||||||
selectConfigSlice,
|
|
||||||
selectActiveTab,
|
|
||||||
],
|
|
||||||
(system, nodes, workflowSettings, dynamicPrompts, canvas, params, upscale, config, activeTabName) => {
|
|
||||||
const reasons: Reason[] = [];
|
|
||||||
|
|
||||||
// Cannot generate if not connected
|
return createSelector(
|
||||||
if (!isConnected) {
|
selectCanvasSlice,
|
||||||
reasons.push({ content: i18n.t('parameters.invoke.systemDisconnected') });
|
selectParamsSlice,
|
||||||
}
|
selectDynamicPromptsSlice,
|
||||||
|
(canvas, params, dynamicPrompts) =>
|
||||||
if (activeTabName === 'workflows') {
|
getReasonsWhyCannotEnqueueCanvasTab({
|
||||||
handleWorkflowsTab({ reasons, nodes, workflowSettings, templates });
|
|
||||||
} else if (activeTabName === 'upscaling') {
|
|
||||||
handleUpscalingTab({ reasons, upscale, config, params });
|
|
||||||
} else {
|
|
||||||
handleCanvasTab({
|
|
||||||
reasons,
|
|
||||||
canvas,
|
|
||||||
params,
|
|
||||||
dynamicPrompts,
|
|
||||||
canvasIsFiltering,
|
|
||||||
canvasIsTransforming,
|
|
||||||
canvasIsRasterizing,
|
|
||||||
canvasIsCompositing,
|
|
||||||
canvasIsSelectingObject,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { isReady: !reasons.length, reasons };
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useIsReadyToEnqueue = () => {
|
|
||||||
const templates = useStore($templates);
|
|
||||||
const isConnected = useStore($isConnected);
|
|
||||||
const canvasManager = useCanvasManagerSafe();
|
|
||||||
const canvasIsFiltering = useStore(canvasManager?.stateApi.$isFiltering ?? $true);
|
|
||||||
const canvasIsTransforming = useStore(canvasManager?.stateApi.$isTransforming ?? $true);
|
|
||||||
const canvasIsRasterizing = useStore(canvasManager?.stateApi.$isRasterizing ?? $true);
|
|
||||||
const canvasIsSelectingObject = useStore(canvasManager?.stateApi.$isSegmenting ?? $true);
|
|
||||||
const canvasIsCompositing = useStore(canvasManager?.compositor.$isBusy ?? $true);
|
|
||||||
const selector = useMemo(
|
|
||||||
() =>
|
|
||||||
createSelector({
|
|
||||||
templates,
|
|
||||||
isConnected,
|
isConnected,
|
||||||
|
canvas,
|
||||||
|
params,
|
||||||
|
dynamicPrompts,
|
||||||
canvasIsFiltering,
|
canvasIsFiltering,
|
||||||
canvasIsTransforming,
|
canvasIsTransforming,
|
||||||
canvasIsRasterizing,
|
canvasIsRasterizing,
|
||||||
canvasIsCompositing,
|
canvasIsCompositing,
|
||||||
canvasIsSelectingObject,
|
canvasIsSelectingObject,
|
||||||
}),
|
})
|
||||||
[
|
|
||||||
templates,
|
|
||||||
isConnected,
|
|
||||||
canvasIsFiltering,
|
|
||||||
canvasIsTransforming,
|
|
||||||
canvasIsRasterizing,
|
|
||||||
canvasIsCompositing,
|
|
||||||
canvasIsSelectingObject,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
const value = useAppSelector(selector);
|
|
||||||
return value;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const buildSelectIsReadyToEnqueueCanvasTab = (arg: {
|
||||||
|
isConnected: boolean;
|
||||||
|
canvasIsFiltering: boolean;
|
||||||
|
canvasIsTransforming: boolean;
|
||||||
|
canvasIsRasterizing: boolean;
|
||||||
|
canvasIsCompositing: boolean;
|
||||||
|
canvasIsSelectingObject: boolean;
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
isConnected,
|
||||||
|
canvasIsFiltering,
|
||||||
|
canvasIsTransforming,
|
||||||
|
canvasIsRasterizing,
|
||||||
|
canvasIsCompositing,
|
||||||
|
canvasIsSelectingObject,
|
||||||
|
} = arg;
|
||||||
|
|
||||||
|
return createSelector(
|
||||||
|
selectCanvasSlice,
|
||||||
|
selectParamsSlice,
|
||||||
|
selectDynamicPromptsSlice,
|
||||||
|
(canvas, params, dynamicPrompts) =>
|
||||||
|
getReasonsWhyCannotEnqueueCanvasTab({
|
||||||
|
isConnected,
|
||||||
|
canvas,
|
||||||
|
params,
|
||||||
|
dynamicPrompts,
|
||||||
|
canvasIsFiltering,
|
||||||
|
canvasIsTransforming,
|
||||||
|
canvasIsRasterizing,
|
||||||
|
canvasIsCompositing,
|
||||||
|
canvasIsSelectingObject,
|
||||||
|
}).length === 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildSelectReasonsWhyCannotEnqueueUpscaleTab = (arg: { isConnected: boolean }) => {
|
||||||
|
const { isConnected } = arg;
|
||||||
|
return createSelector(selectUpscaleSlice, selectConfigSlice, selectParamsSlice, (upscale, config, params) =>
|
||||||
|
getReasonsWhyCannotEnqueueUpscaleTab({ isConnected, upscale, config, params })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildSelectIsReadyToEnqueueUpscaleTab = (arg: { isConnected: boolean }) => {
|
||||||
|
const { isConnected } = arg;
|
||||||
|
|
||||||
|
return createSelector(
|
||||||
|
selectUpscaleSlice,
|
||||||
|
selectConfigSlice,
|
||||||
|
selectParamsSlice,
|
||||||
|
(upscale, config, params) =>
|
||||||
|
getReasonsWhyCannotEnqueueUpscaleTab({ isConnected, upscale, config, params }).length === 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildSelectReasonsWhyCannotEnqueueWorkflowsTab = (arg: { isConnected: boolean; templates: Templates }) => {
|
||||||
|
const { isConnected, templates } = arg;
|
||||||
|
|
||||||
|
return createSelector(selectNodesSlice, selectWorkflowSettingsSlice, (nodes, workflowSettings) =>
|
||||||
|
getReasonsWhyCannotEnqueueWorkflowsTab({
|
||||||
|
isConnected,
|
||||||
|
nodes,
|
||||||
|
workflowSettings,
|
||||||
|
templates,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildSelectIsReadyToEnqueueWorkflowsTab = (arg: { isConnected: boolean; templates: Templates }) => {
|
||||||
|
const { isConnected, templates } = arg;
|
||||||
|
|
||||||
|
return createSelector(
|
||||||
|
selectNodesSlice,
|
||||||
|
selectWorkflowSettingsSlice,
|
||||||
|
(nodes, workflowSettings) =>
|
||||||
|
getReasonsWhyCannotEnqueueWorkflowsTab({
|
||||||
|
isConnected,
|
||||||
|
nodes,
|
||||||
|
workflowSettings,
|
||||||
|
templates,
|
||||||
|
}).length === 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectPromptsCount = createSelector(
|
||||||
|
selectParamsSlice,
|
||||||
|
selectDynamicPromptsSlice,
|
||||||
|
(params, dynamicPrompts) => (getShouldProcessPrompt(params.positivePrompt) ? dynamicPrompts.prompts.length : 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectWorkflowsBatchSize = createSelector(selectNodesSlice, ({ nodes }) =>
|
||||||
|
// The batch size is the product of all batch nodes' collection sizes
|
||||||
|
nodes.filter(isInvocationNode).reduce((batchSize, node) => {
|
||||||
|
if (!isImageFieldCollectionInputInstance(node.data.inputs.images)) {
|
||||||
|
return batchSize;
|
||||||
|
}
|
||||||
|
// If the batch size is not set, default to 1
|
||||||
|
batchSize = batchSize || 1;
|
||||||
|
// Multiply the batch size by the number of images in the batch
|
||||||
|
batchSize = batchSize * (node.data.inputs.images.value?.length ?? 0);
|
||||||
|
|
||||||
|
return batchSize;
|
||||||
|
}, 0)
|
||||||
|
);
|
||||||
@@ -4,7 +4,7 @@ import { ToolChooser } from 'features/controlLayers/components/Tool/ToolChooser'
|
|||||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||||
import { useClearQueue } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
|
import { useClearQueue } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
|
||||||
import { QueueButtonTooltip } from 'features/queue/components/QueueButtonTooltip';
|
import { InvokeButtonTooltip } from 'features/queue/components/InvokeButtonTooltip/InvokeButtonTooltip';
|
||||||
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
|
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
|
||||||
import { useInvoke } from 'features/queue/hooks/useInvoke';
|
import { useInvoke } from 'features/queue/hooks/useInvoke';
|
||||||
import type { UsePanelReturn } from 'features/ui/hooks/usePanel';
|
import type { UsePanelReturn } from 'features/ui/hooks/usePanel';
|
||||||
@@ -62,7 +62,7 @@ const FloatingSidePanelButtons = (props: Props) => {
|
|||||||
flexGrow={1}
|
flexGrow={1}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<QueueButtonTooltip prepend={shift} placement="end">
|
<InvokeButtonTooltip prepend={shift} placement="end">
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={t('queue.queueBack')}
|
aria-label={t('queue.queueBack')}
|
||||||
onClick={shift ? queue.queueFront : queue.queueBack}
|
onClick={shift ? queue.queueFront : queue.queueBack}
|
||||||
@@ -72,7 +72,7 @@ const FloatingSidePanelButtons = (props: Props) => {
|
|||||||
colorScheme="invokeYellow"
|
colorScheme="invokeYellow"
|
||||||
flexGrow={1}
|
flexGrow={1}
|
||||||
/>
|
/>
|
||||||
</QueueButtonTooltip>
|
</InvokeButtonTooltip>
|
||||||
<Tooltip label={t('queue.cancelTooltip')} placement="end">
|
<Tooltip label={t('queue.cancelTooltip')} placement="end">
|
||||||
<IconButton
|
<IconButton
|
||||||
isDisabled={cancelCurrent.isDisabled}
|
isDisabled={cancelCurrent.isDisabled}
|
||||||
|
|||||||
@@ -425,3 +425,7 @@ const resetListQueryData = (
|
|||||||
// we have to manually kick off another query to get the first page and re-initialize the list
|
// we have to manually kick off another query to get the first page and re-initialize the list
|
||||||
dispatch(queueApi.endpoints.listQueueItems.initiate(undefined));
|
dispatch(queueApi.endpoints.listQueueItems.initiate(undefined));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const enqueueMutationFixedCacheKeyOptions = {
|
||||||
|
fixedCacheKey: 'enqueueBatch',
|
||||||
|
} as const;
|
||||||
|
|||||||
Reference in New Issue
Block a user