mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Add Negative Prompts Box
This commit is contained in:
parent
4a026bd46e
commit
7604b36577
@ -43,6 +43,7 @@
|
|||||||
"invoke": "Invoke",
|
"invoke": "Invoke",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"promptPlaceholder": "Type prompt here. [negative tokens], (upweight)++, (downweight)--, swap and blend are available (see docs)",
|
"promptPlaceholder": "Type prompt here. [negative tokens], (upweight)++, (downweight)--, swap and blend are available (see docs)",
|
||||||
|
"negativePrompts": "Negative Prompts",
|
||||||
"sendTo": "Send to",
|
"sendTo": "Send to",
|
||||||
"sendToImg2Img": "Send to Image to Image",
|
"sendToImg2Img": "Send to Image to Image",
|
||||||
"sendToUnifiedCanvas": "Send To Unified Canvas",
|
"sendToUnifiedCanvas": "Send To Unified Canvas",
|
||||||
|
@ -11,7 +11,6 @@ const useClickOutsideWatcher = () => {
|
|||||||
function handleClickOutside(e: MouseEvent) {
|
function handleClickOutside(e: MouseEvent) {
|
||||||
watchers.forEach(({ ref, enable, callback }) => {
|
watchers.forEach(({ ref, enable, callback }) => {
|
||||||
if (enable && ref.current && !ref.current.contains(e.target as Node)) {
|
if (enable && ref.current && !ref.current.contains(e.target as Node)) {
|
||||||
console.log('callback');
|
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
20
invokeai/frontend/src/common/util/getPromptAndNegative.ts
Normal file
20
invokeai/frontend/src/common/util/getPromptAndNegative.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import * as InvokeAI from 'app/invokeai';
|
||||||
|
import promptToString from './promptToString';
|
||||||
|
|
||||||
|
export function getPromptAndNegative(input_prompt: InvokeAI.Prompt) {
|
||||||
|
let prompt: string = promptToString(input_prompt);
|
||||||
|
let negativePrompt: string | null = null;
|
||||||
|
|
||||||
|
const negativePromptRegExp = new RegExp(/(?<=\[)[^\][]*(?=])/, 'gi');
|
||||||
|
const negativePromptMatches = [...prompt.matchAll(negativePromptRegExp)];
|
||||||
|
|
||||||
|
if (negativePromptMatches && negativePromptMatches.length > 0) {
|
||||||
|
negativePrompt = negativePromptMatches.join(', ');
|
||||||
|
prompt = prompt
|
||||||
|
.replaceAll(negativePromptRegExp, '')
|
||||||
|
.replaceAll('[]', '')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [prompt, negativePrompt];
|
||||||
|
}
|
@ -106,6 +106,7 @@ export const frontendToBackendParameters = (
|
|||||||
iterations,
|
iterations,
|
||||||
perlin,
|
perlin,
|
||||||
prompt,
|
prompt,
|
||||||
|
negativePrompt,
|
||||||
sampler,
|
sampler,
|
||||||
seamBlur,
|
seamBlur,
|
||||||
seamless,
|
seamless,
|
||||||
@ -155,6 +156,10 @@ export const frontendToBackendParameters = (
|
|||||||
let esrganParameters: false | BackendEsrGanParameters = false;
|
let esrganParameters: false | BackendEsrGanParameters = false;
|
||||||
let facetoolParameters: false | BackendFacetoolParameters = false;
|
let facetoolParameters: false | BackendFacetoolParameters = false;
|
||||||
|
|
||||||
|
if (negativePrompt !== '') {
|
||||||
|
generationParameters.prompt = `${prompt} [${negativePrompt}]`;
|
||||||
|
}
|
||||||
|
|
||||||
generationParameters.seed = shouldRandomizeSeed
|
generationParameters.seed = shouldRandomizeSeed
|
||||||
? randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX)
|
? randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX)
|
||||||
: seed;
|
: seed;
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
setAllParameters,
|
setAllParameters,
|
||||||
setInitialImage,
|
setInitialImage,
|
||||||
setIsLightBoxOpen,
|
setIsLightBoxOpen,
|
||||||
|
setNegativePrompt,
|
||||||
setPrompt,
|
setPrompt,
|
||||||
setSeed,
|
setSeed,
|
||||||
setShouldShowImageDetails,
|
setShouldShowImageDetails,
|
||||||
@ -44,6 +45,7 @@ import { GalleryState } from 'features/gallery/store/gallerySlice';
|
|||||||
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
|
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
|
||||||
import IAIPopover from 'common/components/IAIPopover';
|
import IAIPopover from 'common/components/IAIPopover';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { getPromptAndNegative } from 'common/util/getPromptAndNegative';
|
||||||
|
|
||||||
const systemSelector = createSelector(
|
const systemSelector = createSelector(
|
||||||
[
|
[
|
||||||
@ -241,9 +243,18 @@ const CurrentImageButtons = () => {
|
|||||||
[currentImage]
|
[currentImage]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClickUsePrompt = () =>
|
const handleClickUsePrompt = () => {
|
||||||
currentImage?.metadata?.image?.prompt &&
|
if (currentImage?.metadata?.image?.prompt) {
|
||||||
dispatch(setPrompt(currentImage.metadata.image.prompt));
|
const [prompt, negativePrompt] = getPromptAndNegative(
|
||||||
|
currentImage?.metadata?.image?.prompt
|
||||||
|
);
|
||||||
|
|
||||||
|
prompt && dispatch(setPrompt(prompt));
|
||||||
|
negativePrompt
|
||||||
|
? dispatch(setNegativePrompt(negativePrompt))
|
||||||
|
: dispatch(setNegativePrompt(''));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'p',
|
'p',
|
||||||
|
@ -10,9 +10,10 @@ import { DragEvent, memo, useState } from 'react';
|
|||||||
import {
|
import {
|
||||||
setActiveTab,
|
setActiveTab,
|
||||||
setAllImageToImageParameters,
|
setAllImageToImageParameters,
|
||||||
setAllTextToImageParameters,
|
setAllParameters,
|
||||||
setInitialImage,
|
setInitialImage,
|
||||||
setIsLightBoxOpen,
|
setIsLightBoxOpen,
|
||||||
|
setNegativePrompt,
|
||||||
setPrompt,
|
setPrompt,
|
||||||
setSeed,
|
setSeed,
|
||||||
} from 'features/options/store/optionsSlice';
|
} from 'features/options/store/optionsSlice';
|
||||||
@ -24,6 +25,7 @@ import {
|
|||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import { hoverableImageSelector } from 'features/gallery/store/gallerySliceSelectors';
|
import { hoverableImageSelector } from 'features/gallery/store/gallerySliceSelectors';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { getPromptAndNegative } from 'common/util/getPromptAndNegative';
|
||||||
|
|
||||||
interface HoverableImageProps {
|
interface HoverableImageProps {
|
||||||
image: InvokeAI.Image;
|
image: InvokeAI.Image;
|
||||||
@ -62,7 +64,17 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
const handleMouseOut = () => setIsHovered(false);
|
const handleMouseOut = () => setIsHovered(false);
|
||||||
|
|
||||||
const handleUsePrompt = () => {
|
const handleUsePrompt = () => {
|
||||||
image.metadata && dispatch(setPrompt(image.metadata.image.prompt));
|
if (image.metadata) {
|
||||||
|
const [prompt, negativePrompt] = getPromptAndNegative(
|
||||||
|
image.metadata?.image?.prompt
|
||||||
|
);
|
||||||
|
|
||||||
|
prompt && dispatch(setPrompt(prompt));
|
||||||
|
negativePrompt
|
||||||
|
? dispatch(setNegativePrompt(negativePrompt))
|
||||||
|
: dispatch(setNegativePrompt(''));
|
||||||
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t('toast:promptSet'),
|
title: t('toast:promptSet'),
|
||||||
status: 'success',
|
status: 'success',
|
||||||
@ -115,7 +127,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUseAllParameters = () => {
|
const handleUseAllParameters = () => {
|
||||||
metadata && dispatch(setAllTextToImageParameters(metadata));
|
metadata && dispatch(setAllParameters(metadata));
|
||||||
toast({
|
toast({
|
||||||
title: t('toast:parametersSet'),
|
title: t('toast:parametersSet'),
|
||||||
status: 'success',
|
status: 'success',
|
||||||
|
@ -38,7 +38,6 @@ export const uploadImage =
|
|||||||
});
|
});
|
||||||
|
|
||||||
const image = (await response.json()) as InvokeAI.ImageUploadResponse;
|
const image = (await response.json()) as InvokeAI.ImageUploadResponse;
|
||||||
console.log(image);
|
|
||||||
const newImage: InvokeAI.Image = {
|
const newImage: InvokeAI.Image = {
|
||||||
uuid: uuidv4(),
|
uuid: uuidv4(),
|
||||||
category: 'user',
|
category: 'user',
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import { FormControl, Textarea } from '@chakra-ui/react';
|
||||||
|
import type { RootState } from 'app/store';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
|
import { setNegativePrompt } from 'features/options/store/optionsSlice';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export function NegativePromptInput() {
|
||||||
|
const negativePrompt = useAppSelector(
|
||||||
|
(state: RootState) => state.options.negativePrompt
|
||||||
|
);
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
id="negativePrompt"
|
||||||
|
name="negativePrompt"
|
||||||
|
value={negativePrompt}
|
||||||
|
onChange={(e) => dispatch(setNegativePrompt(e.target.value))}
|
||||||
|
background="var(--prompt-bg-color)"
|
||||||
|
placeholder={t('options:negativePrompts')}
|
||||||
|
_placeholder={{ fontSize: '0.8rem' }}
|
||||||
|
borderColor="var(--border-color)"
|
||||||
|
_hover={{
|
||||||
|
borderColor: 'var(--border-color-light)',
|
||||||
|
}}
|
||||||
|
_focusVisible={{
|
||||||
|
borderColor: 'var(--border-color-invalid)',
|
||||||
|
boxShadow: '0 0 10px var(--box-shadow-color-invalid)',
|
||||||
|
}}
|
||||||
|
fontSize="0.9rem"
|
||||||
|
color="var(--text-color-secondary)"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
@ -5,6 +5,7 @@ import promptToString from 'common/util/promptToString';
|
|||||||
import { seedWeightsToString } from 'common/util/seedWeightPairs';
|
import { seedWeightsToString } from 'common/util/seedWeightPairs';
|
||||||
import { FACETOOL_TYPES } from 'app/constants';
|
import { FACETOOL_TYPES } from 'app/constants';
|
||||||
import { InvokeTabName, tabMap } from 'features/tabs/tabMap';
|
import { InvokeTabName, tabMap } from 'features/tabs/tabMap';
|
||||||
|
import { getPromptAndNegative } from 'common/util/getPromptAndNegative';
|
||||||
|
|
||||||
export type UpscalingLevel = 2 | 4;
|
export type UpscalingLevel = 2 | 4;
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ export interface OptionsState {
|
|||||||
optionsPanelScrollPosition: number;
|
optionsPanelScrollPosition: number;
|
||||||
perlin: number;
|
perlin: number;
|
||||||
prompt: string;
|
prompt: string;
|
||||||
|
negativePrompt: string;
|
||||||
sampler: string;
|
sampler: string;
|
||||||
seamBlur: number;
|
seamBlur: number;
|
||||||
seamless: boolean;
|
seamless: boolean;
|
||||||
@ -77,6 +79,7 @@ const initialOptionsState: OptionsState = {
|
|||||||
optionsPanelScrollPosition: 0,
|
optionsPanelScrollPosition: 0,
|
||||||
perlin: 0,
|
perlin: 0,
|
||||||
prompt: '',
|
prompt: '',
|
||||||
|
negativePrompt: '',
|
||||||
sampler: 'k_lms',
|
sampler: 'k_lms',
|
||||||
seamBlur: 16,
|
seamBlur: 16,
|
||||||
seamless: false,
|
seamless: false,
|
||||||
@ -123,6 +126,17 @@ export const optionsSlice = createSlice({
|
|||||||
state.prompt = promptToString(newPrompt);
|
state.prompt = promptToString(newPrompt);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setNegativePrompt: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<string | InvokeAI.Prompt>
|
||||||
|
) => {
|
||||||
|
const newPrompt = action.payload;
|
||||||
|
if (typeof newPrompt === 'string') {
|
||||||
|
state.negativePrompt = newPrompt;
|
||||||
|
} else {
|
||||||
|
state.negativePrompt = promptToString(newPrompt);
|
||||||
|
}
|
||||||
|
},
|
||||||
setIterations: (state, action: PayloadAction<number>) => {
|
setIterations: (state, action: PayloadAction<number>) => {
|
||||||
state.iterations = action.payload;
|
state.iterations = action.payload;
|
||||||
},
|
},
|
||||||
@ -307,7 +321,14 @@ export const optionsSlice = createSlice({
|
|||||||
state.shouldRandomizeSeed = false;
|
state.shouldRandomizeSeed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt) state.prompt = promptToString(prompt);
|
if (prompt) {
|
||||||
|
const [promptOnly, negativePrompt] = getPromptAndNegative(prompt);
|
||||||
|
if (promptOnly) state.prompt = promptOnly;
|
||||||
|
negativePrompt
|
||||||
|
? (state.negativePrompt = negativePrompt)
|
||||||
|
: (state.negativePrompt = '');
|
||||||
|
}
|
||||||
|
|
||||||
if (sampler) state.sampler = sampler;
|
if (sampler) state.sampler = sampler;
|
||||||
if (steps) state.steps = steps;
|
if (steps) state.steps = steps;
|
||||||
if (cfg_scale) state.cfgScale = cfg_scale;
|
if (cfg_scale) state.cfgScale = cfg_scale;
|
||||||
@ -448,6 +469,7 @@ export const {
|
|||||||
setParameter,
|
setParameter,
|
||||||
setPerlin,
|
setPerlin,
|
||||||
setPrompt,
|
setPrompt,
|
||||||
|
setNegativePrompt,
|
||||||
setSampler,
|
setSampler,
|
||||||
setSeamBlur,
|
setSeamBlur,
|
||||||
setSeamless,
|
setSeamless,
|
||||||
|
@ -19,6 +19,8 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
|||||||
import InvokeOptionsPanel from 'features/tabs/components/InvokeOptionsPanel';
|
import InvokeOptionsPanel from 'features/tabs/components/InvokeOptionsPanel';
|
||||||
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
|
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
import { NegativePromptInput } from 'features/options/components/PromptInput/NegativePromptInput';
|
||||||
|
|
||||||
export default function ImageToImagePanel() {
|
export default function ImageToImagePanel() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -67,7 +69,10 @@ export default function ImageToImagePanel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<InvokeOptionsPanel>
|
<InvokeOptionsPanel>
|
||||||
<PromptInput />
|
<Flex flexDir="column" rowGap="0.5rem">
|
||||||
|
<PromptInput />
|
||||||
|
<NegativePromptInput />
|
||||||
|
</Flex>
|
||||||
<ProcessButtons />
|
<ProcessButtons />
|
||||||
<MainOptions />
|
<MainOptions />
|
||||||
<ImageToImageStrength
|
<ImageToImageStrength
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
import { Feature } from 'app/features';
|
import { Feature } from 'app/features';
|
||||||
import FaceRestoreOptions from 'features/options/components/AdvancedOptions/FaceRestore/FaceRestoreOptions';
|
import FaceRestoreOptions from 'features/options/components/AdvancedOptions/FaceRestore/FaceRestoreOptions';
|
||||||
import FaceRestoreToggle from 'features/options/components/AdvancedOptions/FaceRestore/FaceRestoreToggle';
|
import FaceRestoreToggle from 'features/options/components/AdvancedOptions/FaceRestore/FaceRestoreToggle';
|
||||||
@ -10,6 +11,7 @@ import VariationsOptions from 'features/options/components/AdvancedOptions/Varia
|
|||||||
import MainOptions from 'features/options/components/MainOptions/MainOptions';
|
import MainOptions from 'features/options/components/MainOptions/MainOptions';
|
||||||
import OptionsAccordion from 'features/options/components/OptionsAccordion';
|
import OptionsAccordion from 'features/options/components/OptionsAccordion';
|
||||||
import ProcessButtons from 'features/options/components/ProcessButtons/ProcessButtons';
|
import ProcessButtons from 'features/options/components/ProcessButtons/ProcessButtons';
|
||||||
|
import { NegativePromptInput } from 'features/options/components/PromptInput/NegativePromptInput';
|
||||||
import PromptInput from 'features/options/components/PromptInput/PromptInput';
|
import PromptInput from 'features/options/components/PromptInput/PromptInput';
|
||||||
import InvokeOptionsPanel from 'features/tabs/components/InvokeOptionsPanel';
|
import InvokeOptionsPanel from 'features/tabs/components/InvokeOptionsPanel';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -50,7 +52,10 @@ export default function TextToImagePanel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<InvokeOptionsPanel>
|
<InvokeOptionsPanel>
|
||||||
<PromptInput />
|
<Flex flexDir="column" rowGap="0.5rem">
|
||||||
|
<PromptInput />
|
||||||
|
<NegativePromptInput />
|
||||||
|
</Flex>
|
||||||
<ProcessButtons />
|
<ProcessButtons />
|
||||||
<MainOptions />
|
<MainOptions />
|
||||||
<OptionsAccordion accordionInfo={textToImageAccordions} />
|
<OptionsAccordion accordionInfo={textToImageAccordions} />
|
||||||
|
@ -13,6 +13,8 @@ import InvokeOptionsPanel from 'features/tabs/components/InvokeOptionsPanel';
|
|||||||
import BoundingBoxSettings from 'features/options/components/AdvancedOptions/Canvas/BoundingBoxSettings/BoundingBoxSettings';
|
import BoundingBoxSettings from 'features/options/components/AdvancedOptions/Canvas/BoundingBoxSettings/BoundingBoxSettings';
|
||||||
import InfillAndScalingOptions from 'features/options/components/AdvancedOptions/Canvas/InfillAndScalingOptions';
|
import InfillAndScalingOptions from 'features/options/components/AdvancedOptions/Canvas/InfillAndScalingOptions';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
import { NegativePromptInput } from 'features/options/components/PromptInput/NegativePromptInput';
|
||||||
|
|
||||||
export default function UnifiedCanvasPanel() {
|
export default function UnifiedCanvasPanel() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -48,7 +50,10 @@ export default function UnifiedCanvasPanel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<InvokeOptionsPanel>
|
<InvokeOptionsPanel>
|
||||||
<PromptInput />
|
<Flex flexDir="column" rowGap="0.5rem">
|
||||||
|
<PromptInput />
|
||||||
|
<NegativePromptInput />
|
||||||
|
</Flex>
|
||||||
<ProcessButtons />
|
<ProcessButtons />
|
||||||
<MainOptions />
|
<MainOptions />
|
||||||
<ImageToImageStrength
|
<ImageToImageStrength
|
||||||
|
Loading…
Reference in New Issue
Block a user