mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): toggleable negative prompt
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
selectIsChatGTP4o,
|
||||
selectIsChatGPT4o,
|
||||
selectIsCogView4,
|
||||
selectIsFluxKontext,
|
||||
selectIsImagen3,
|
||||
@@ -17,8 +17,8 @@ export const useIsEntityTypeEnabled = (entityType: CanvasEntityType) => {
|
||||
const isCogView4 = useAppSelector(selectIsCogView4);
|
||||
const isImagen3 = useAppSelector(selectIsImagen3);
|
||||
const isImagen4 = useAppSelector(selectIsImagen4);
|
||||
const isChatGPT4o = useAppSelector(selectIsChatGTP4o);
|
||||
const isFluxKontext = useAppSelector(selectIsFluxKontext);
|
||||
const isChatGPT4o = useAppSelector(selectIsChatGPT4o);
|
||||
|
||||
const isEntityTypeEnabled = useMemo<boolean>(() => {
|
||||
switch (entityType) {
|
||||
|
||||
@@ -14,6 +14,8 @@ import type {
|
||||
ParameterControlLoRAModel,
|
||||
ParameterGuidance,
|
||||
ParameterModel,
|
||||
ParameterNegativePrompt,
|
||||
ParameterPositivePrompt,
|
||||
ParameterPrecision,
|
||||
ParameterScheduler,
|
||||
ParameterSDXLRefinerModel,
|
||||
@@ -124,10 +126,10 @@ export const paramsSlice = createSlice({
|
||||
shouldUseCpuNoiseChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldUseCpuNoise = action.payload;
|
||||
},
|
||||
positivePromptChanged: (state, action: PayloadAction<string>) => {
|
||||
positivePromptChanged: (state, action: PayloadAction<ParameterPositivePrompt>) => {
|
||||
state.positivePrompt = action.payload;
|
||||
},
|
||||
negativePromptChanged: (state, action: PayloadAction<string>) => {
|
||||
negativePromptChanged: (state, action: PayloadAction<ParameterNegativePrompt>) => {
|
||||
state.negativePrompt = action.payload;
|
||||
},
|
||||
positivePrompt2Changed: (state, action: PayloadAction<string>) => {
|
||||
@@ -273,8 +275,8 @@ export const selectIsSD3 = createParamsSelector((params) => params.model?.base =
|
||||
export const selectIsCogView4 = createParamsSelector((params) => params.model?.base === 'cogview4');
|
||||
export const selectIsImagen3 = createParamsSelector((params) => params.model?.base === 'imagen3');
|
||||
export const selectIsImagen4 = createParamsSelector((params) => params.model?.base === 'imagen4');
|
||||
export const selectIsChatGTP4o = createParamsSelector((params) => params.model?.base === 'chatgpt-4o');
|
||||
export const selectIsFluxKontext = createParamsSelector((params) => params.model?.base === 'flux-kontext');
|
||||
export const selectIsChatGPT4o = createParamsSelector((params) => params.model?.base === 'chatgpt-4o');
|
||||
|
||||
export const selectModel = createParamsSelector((params) => params.model);
|
||||
export const selectModelKey = createParamsSelector((params) => params.model?.key);
|
||||
@@ -306,6 +308,12 @@ export const selectImg2imgStrength = createParamsSelector((params) => params.img
|
||||
export const selectOptimizedDenoisingEnabled = createParamsSelector((params) => params.optimizedDenoisingEnabled);
|
||||
export const selectPositivePrompt = createParamsSelector((params) => params.positivePrompt);
|
||||
export const selectNegativePrompt = createParamsSelector((params) => params.negativePrompt);
|
||||
export const selectNegativePromptWithFallback = createParamsSelector((params) => params.negativePrompt ?? '');
|
||||
export const selectHasNegativePrompt = createParamsSelector((params) => params.negativePrompt !== null);
|
||||
export const selectModelSupportsNegativePrompt = createSelector(
|
||||
[selectIsFLUX, selectIsChatGPT4o],
|
||||
(isFLUX, isChatGPT4o) => !isFLUX && !isChatGPT4o
|
||||
);
|
||||
export const selectPositivePrompt2 = createParamsSelector((params) => params.positivePrompt2);
|
||||
export const selectNegativePrompt2 = createParamsSelector((params) => params.negativePrompt2);
|
||||
export const selectShouldConcatPrompts = createParamsSelector((params) => params.shouldConcatPrompts);
|
||||
|
||||
@@ -522,7 +522,8 @@ const zParamsState = z.object({
|
||||
clipSkip: z.number().default(0),
|
||||
shouldUseCpuNoise: z.boolean().default(true),
|
||||
positivePrompt: zParameterPositivePrompt.default(''),
|
||||
negativePrompt: zParameterNegativePrompt.default(''),
|
||||
// Negative prompt may be disabled, in which case it will be null
|
||||
negativePrompt: zParameterNegativePrompt.default(null),
|
||||
positivePrompt2: zParameterPositiveStylePromptSDXL.default(''),
|
||||
negativePrompt2: zParameterNegativeStylePromptSDXL.default(''),
|
||||
shouldConcatPrompts: z.boolean().default(true),
|
||||
|
||||
@@ -121,8 +121,8 @@ export const useImageActions = (imageDTO: ImageDTO) => {
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
let positivePrompt;
|
||||
let negativePrompt;
|
||||
let positivePrompt: string;
|
||||
let negativePrompt: string;
|
||||
|
||||
try {
|
||||
positivePrompt = await handlers.positivePrompt.parse(metadata);
|
||||
@@ -130,7 +130,7 @@ export const useImageActions = (imageDTO: ImageDTO) => {
|
||||
positivePrompt = '';
|
||||
}
|
||||
try {
|
||||
negativePrompt = await handlers.negativePrompt.parse(metadata);
|
||||
negativePrompt = (await handlers.negativePrompt.parse(metadata)) ?? '';
|
||||
} catch (error) {
|
||||
negativePrompt = '';
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ export const selectPresetModifiedPrompts = createSelector(
|
||||
selectStylePresetSlice,
|
||||
selectListStylePresetsRequestState,
|
||||
(params, stylePresetSlice, listStylePresetsRequestState) => {
|
||||
const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } = params;
|
||||
const negativePrompt = params.negativePrompt ?? '';
|
||||
const { positivePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } = params;
|
||||
const { activeStylePresetId } = stylePresetSlice;
|
||||
|
||||
if (activeStylePresetId) {
|
||||
@@ -72,7 +73,7 @@ export const selectPresetModifiedPrompts = createSelector(
|
||||
|
||||
const presetModifiedNegativePrompt = buildPresetModifiedPrompt(
|
||||
activeStylePreset.preset_data.negative_prompt,
|
||||
negativePrompt
|
||||
negativePrompt ?? ''
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { InformationalPopover } from 'common/components/InformationalPopover/Inf
|
||||
import { bboxAspectRatioIdChanged } from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import {
|
||||
selectIsChatGTP4o,
|
||||
selectIsChatGPT4o,
|
||||
selectIsFluxKontext,
|
||||
selectIsImagen3,
|
||||
selectIsImagen4,
|
||||
@@ -28,7 +28,7 @@ export const BboxAspectRatioSelect = memo(() => {
|
||||
const id = useAppSelector(selectAspectRatioID);
|
||||
const isStaging = useAppSelector(selectIsStaging);
|
||||
const isImagen3 = useAppSelector(selectIsImagen3);
|
||||
const isChatGPT4o = useAppSelector(selectIsChatGTP4o);
|
||||
const isChatGPT4o = useAppSelector(selectIsChatGPT4o);
|
||||
const isImagen4 = useAppSelector(selectIsImagen4);
|
||||
const isFluxKontext = useAppSelector(selectIsFluxKontext);
|
||||
const options = useMemo(() => {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { IconButton, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { negativePromptChanged, selectHasNegativePrompt } from 'features/controlLayers/store/paramsSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiPlusMinusBold } from 'react-icons/pi';
|
||||
|
||||
export const NegativePromptToggleButton = memo(() => {
|
||||
const hasNegativePrompt = useAppSelector(selectHasNegativePrompt);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (hasNegativePrompt) {
|
||||
dispatch(negativePromptChanged(null));
|
||||
} else {
|
||||
dispatch(negativePromptChanged(''));
|
||||
}
|
||||
}, [dispatch, hasNegativePrompt]);
|
||||
|
||||
const label = useMemo(
|
||||
() => (hasNegativePrompt ? 'Remove Negative Prompt' : 'Add Negative Prompt'),
|
||||
[hasNegativePrompt]
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip label={label}>
|
||||
<IconButton
|
||||
aria-label={label}
|
||||
onClick={onClick}
|
||||
icon={<PiPlusMinusBold size={14} />}
|
||||
variant="promptOverlay"
|
||||
fontSize={12}
|
||||
px={0.5}
|
||||
colorScheme={hasNegativePrompt ? 'invokeBlue' : 'base'}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
});
|
||||
|
||||
NegativePromptToggleButton.displayName = 'NegativePromptToggleButton';
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { usePersistedTextAreaSize } from 'common/hooks/usePersistedTextareaSize';
|
||||
import { negativePromptChanged, selectNegativePrompt } from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
negativePromptChanged,
|
||||
selectNegativePromptWithFallback,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel';
|
||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||
import { ViewModePrompt } from 'features/parameters/components/Prompts/ViewModePrompt';
|
||||
@@ -23,7 +26,7 @@ const persistOptions: Parameters<typeof usePersistedTextAreaSize>[2] = {
|
||||
|
||||
export const ParamNegativePrompt = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const prompt = useAppSelector(selectNegativePrompt);
|
||||
const prompt = useAppSelector(selectNegativePromptWithFallback);
|
||||
const viewMode = useAppSelector(selectStylePresetViewMode);
|
||||
const activeStylePresetId = useAppSelector(selectStylePresetActivePresetId);
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { usePersistedTextAreaSize } from 'common/hooks/usePersistedTextareaSize';
|
||||
import { positivePromptChanged, selectBase, selectPositivePrompt } from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
positivePromptChanged,
|
||||
selectBase,
|
||||
selectModelSupportsNegativePrompt,
|
||||
selectPositivePrompt,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton';
|
||||
import { NegativePromptToggleButton } from 'features/parameters/components/Core/NegativePromptToggleButton';
|
||||
import { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel';
|
||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||
import { ViewModePrompt } from 'features/parameters/components/Prompts/ViewModePrompt';
|
||||
@@ -32,6 +38,7 @@ export const ParamPositivePrompt = memo(() => {
|
||||
const baseModel = useAppSelector(selectBase);
|
||||
const viewMode = useAppSelector(selectStylePresetViewMode);
|
||||
const activeStylePresetId = useAppSelector(selectStylePresetActivePresetId);
|
||||
const modelSupportsNegativePrompt = useAppSelector(selectModelSupportsNegativePrompt);
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
usePersistedTextAreaSize('positive_prompt', textareaRef, persistOptions);
|
||||
@@ -98,8 +105,9 @@ export const ParamPositivePrompt = memo(() => {
|
||||
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||
{baseModel === 'sdxl' && <SDXLConcatButton />}
|
||||
<ShowDynamicPromptsPreviewButton />
|
||||
{modelSupportsNegativePrompt && <NegativePromptToggleButton />}
|
||||
</PromptOverlayButtonWrapper>
|
||||
<PromptLabel label={t('parameters.positivePromptPlaceholder')} />
|
||||
<PromptLabel label="Prompt" />
|
||||
{viewMode && (
|
||||
<ViewModePrompt
|
||||
prompt={prompt}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { RefImageList } from 'features/controlLayers/components/RefImage/RefImageList';
|
||||
import { createParamsSelector, selectIsChatGTP4o, selectIsFLUX } from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
createParamsSelector,
|
||||
selectHasNegativePrompt,
|
||||
selectModelSupportsNegativePrompt,
|
||||
} from 'features/controlLayers/store/paramsSlice';
|
||||
import { ParamNegativePrompt } from 'features/parameters/components/Core/ParamNegativePrompt';
|
||||
import { ParamPositivePrompt } from 'features/parameters/components/Core/ParamPositivePrompt';
|
||||
import { ParamSDXLNegativeStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt';
|
||||
@@ -16,15 +20,15 @@ const selectWithStylePrompts = createParamsSelector((params) => {
|
||||
|
||||
export const Prompts = memo(() => {
|
||||
const withStylePrompts = useAppSelector(selectWithStylePrompts);
|
||||
const isFLUX = useAppSelector(selectIsFLUX);
|
||||
const isChatGPT4o = useAppSelector(selectIsChatGTP4o);
|
||||
const modelSupportsNegativePrompt = useAppSelector(selectModelSupportsNegativePrompt);
|
||||
const hasNegativePrompt = useAppSelector(selectHasNegativePrompt);
|
||||
return (
|
||||
<Flex flexDir="column" gap={2}>
|
||||
<ParamPositivePrompt />
|
||||
{withStylePrompts && <ParamSDXLPositiveStylePrompt />}
|
||||
<RefImageList />
|
||||
{!isFLUX && !isChatGPT4o && <ParamNegativePrompt />}
|
||||
{modelSupportsNegativePrompt && hasNegativePrompt && <ParamNegativePrompt />}
|
||||
{withStylePrompts && <ParamSDXLNegativeStylePrompt />}
|
||||
<RefImageList />
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
selectIsChatGTP4o,
|
||||
selectIsChatGPT4o,
|
||||
selectIsFluxKontext,
|
||||
selectIsImagen3,
|
||||
selectIsImagen4,
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
export const useIsApiModel = () => {
|
||||
const isImagen3 = useAppSelector(selectIsImagen3);
|
||||
const isImagen4 = useAppSelector(selectIsImagen4);
|
||||
const isChatGPT4o = useAppSelector(selectIsChatGTP4o);
|
||||
const isFluxKontext = useAppSelector(selectIsFluxKontext);
|
||||
const isChatGPT4o = useAppSelector(selectIsChatGPT4o);
|
||||
|
||||
return isImagen3 || isImagen4 || isChatGPT4o || isFluxKontext;
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ export type ParameterPositivePrompt = z.infer<typeof zParameterPositivePrompt>;
|
||||
// #endregion
|
||||
|
||||
// #region Negative prompt
|
||||
export const [zParameterNegativePrompt, isParameterNegativePrompt] = buildParameter(z.string());
|
||||
export const [zParameterNegativePrompt, isParameterNegativePrompt] = buildParameter(z.string().nullable());
|
||||
export type ParameterNegativePrompt = z.infer<typeof zParameterNegativePrompt>;
|
||||
// #endregion
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectNegativePrompt, selectPositivePrompt } from 'features/controlLayers/store/paramsSlice';
|
||||
import { selectNegativePromptWithFallback, selectPositivePrompt } from 'features/controlLayers/store/paramsSlice';
|
||||
import { selectStylePresetActivePresetId } from 'features/stylePresets/store/stylePresetSlice';
|
||||
import { useListStylePresetsQuery } from 'services/api/endpoints/stylePresets';
|
||||
|
||||
@@ -13,7 +13,7 @@ export const buildPresetModifiedPrompt = (presetPrompt: string, currentPrompt: s
|
||||
|
||||
export const usePresetModifiedPrompts = () => {
|
||||
const positivePrompt = useAppSelector(selectPositivePrompt);
|
||||
const negativePrompt = useAppSelector(selectNegativePrompt);
|
||||
const negativePrompt = useAppSelector(selectNegativePromptWithFallback);
|
||||
const activeStylePresetId = useAppSelector(selectStylePresetActivePresetId);
|
||||
|
||||
const { activeStylePreset } = useListStylePresetsQuery(undefined, {
|
||||
|
||||
Reference in New Issue
Block a user