fix(ui): use prompt bug when prompt has colon

This bug is related to the format in which we stored prompts for some time: an array of weighted subprompts.

This caused some strife when recalling a prompt if the prompt had colons in it, due to our recently introduced handling of negative prompts.

Currently there is no need to store a prompt as anything other than a string, so we revert to doing that.

Compatibility with structured prompts is maintained via helper hook.
This commit is contained in:
psychedelicious 2023-02-22 20:25:13 +11:00
parent ab018ccdfe
commit 7ffaa17551
8 changed files with 54 additions and 41 deletions

View File

@ -1383,13 +1383,6 @@ class InvokeAIWebServer:
# semantic drift # semantic drift
rfc_dict["sampler"] = parameters["sampler_name"] rfc_dict["sampler"] = parameters["sampler_name"]
# display weighted subprompts (liable to change)
subprompts = split_weighted_subprompts(
parameters["prompt"], skip_normalize=True
)
subprompts = [{"prompt": x[0], "weight": x[1]} for x in subprompts]
rfc_dict["prompt"] = subprompts
# 'variations' should always exist and be an array, empty or consisting of {'seed': seed, 'weight': weight} pairs # 'variations' should always exist and be an array, empty or consisting of {'seed': seed, 'weight': weight} pairs
variations = [] variations = []

View File

@ -29,7 +29,8 @@ export declare type PromptItem = {
weight: number; weight: number;
}; };
export declare type Prompt = Array<PromptItem>; // TECHDEBT: We need to retain compatibility with plain prompt strings and the structure Prompt type
export declare type Prompt = Array<PromptItem> | string;
export declare type SeedWeightPair = { export declare type SeedWeightPair = {
seed: number; seed: number;

View File

@ -1,9 +1,11 @@
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/invokeai';
import promptToString from './promptToString'; import promptToString from './promptToString';
export function getPromptAndNegative(input_prompt: InvokeAI.Prompt) { export function getPromptAndNegative(inputPrompt: InvokeAI.Prompt) {
let prompt: string = promptToString(input_prompt); let prompt: string =
let negativePrompt: string | null = null; typeof inputPrompt === 'string' ? inputPrompt : promptToString(inputPrompt);
let negativePrompt = '';
// Matches all negative prompts, 1st capturing group is the prompt itself // Matches all negative prompts, 1st capturing group is the prompt itself
const negativePromptRegExp = new RegExp(/\[([^\][]*)]/, 'gi'); const negativePromptRegExp = new RegExp(/\[([^\][]*)]/, 'gi');

View File

@ -1,6 +1,10 @@
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/invokeai';
const promptToString = (prompt: InvokeAI.Prompt): string => { const promptToString = (prompt: InvokeAI.Prompt): string => {
if (typeof prompt === 'string') {
return prompt;
}
if (prompt.length === 1) { if (prompt.length === 1) {
return prompt[0].prompt; return prompt[0].prompt;
} }

View File

@ -7,7 +7,6 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover'; import IAIPopover from 'common/components/IAIPopover';
import { getPromptAndNegative } from 'common/util/getPromptAndNegative';
import { import {
setDoesCanvasNeedScaling, setDoesCanvasNeedScaling,
setInitialCanvasImage, setInitialCanvasImage,
@ -20,8 +19,6 @@ import UpscaleSettings from 'features/parameters/components/AdvancedParameters/U
import { import {
setAllParameters, setAllParameters,
setInitialImage, setInitialImage,
setNegativePrompt,
setPrompt,
setSeed, setSeed,
} from 'features/parameters/store/generationSlice'; } from 'features/parameters/store/generationSlice';
import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors'; import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors';
@ -53,6 +50,8 @@ import {
} from 'react-icons/fa'; } from 'react-icons/fa';
import { gallerySelector } from '../store/gallerySelectors'; import { gallerySelector } from '../store/gallerySelectors';
import DeleteImageModal from './DeleteImageModal'; import DeleteImageModal from './DeleteImageModal';
import { useCallback } from 'react';
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
const currentImageButtonsSelector = createSelector( const currentImageButtonsSelector = createSelector(
[ [
@ -125,6 +124,7 @@ const CurrentImageButtons = () => {
const toast = useToast(); const toast = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const setBothPrompts = useSetBothPrompts();
const handleClickUseAsInitialImage = () => { const handleClickUseAsInitialImage = () => {
if (!currentImage) return; if (!currentImage) return;
@ -253,18 +253,11 @@ const CurrentImageButtons = () => {
[currentImage] [currentImage]
); );
const handleClickUsePrompt = () => { const handleClickUsePrompt = useCallback(() => {
if (currentImage?.metadata?.image?.prompt) { if (currentImage?.metadata?.image?.prompt) {
const [prompt, negativePrompt] = getPromptAndNegative( setBothPrompts(currentImage?.metadata?.image?.prompt);
currentImage?.metadata?.image?.prompt
);
prompt && dispatch(setPrompt(prompt));
negativePrompt
? dispatch(setNegativePrompt(negativePrompt))
: dispatch(setNegativePrompt(''));
} }
}; }, [currentImage?.metadata?.image?.prompt, setBothPrompts]);
useHotkeys( useHotkeys(
'p', 'p',

View File

@ -8,8 +8,6 @@ import {
setAllImageToImageParameters, setAllImageToImageParameters,
setAllParameters, setAllParameters,
setInitialImage, setInitialImage,
setNegativePrompt,
setPrompt,
setSeed, setSeed,
} from 'features/parameters/store/generationSlice'; } from 'features/parameters/store/generationSlice';
import { DragEvent, memo, useState } from 'react'; import { DragEvent, memo, useState } from 'react';
@ -18,7 +16,6 @@ import DeleteImageModal from './DeleteImageModal';
import * as ContextMenu from '@radix-ui/react-context-menu'; import * as ContextMenu from '@radix-ui/react-context-menu';
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/invokeai';
import { getPromptAndNegative } from 'common/util/getPromptAndNegative';
import { import {
resizeAndScaleCanvas, resizeAndScaleCanvas,
setInitialCanvasImage, setInitialCanvasImage,
@ -26,6 +23,7 @@ import {
import { hoverableImageSelector } from 'features/gallery/store/gallerySelectors'; import { hoverableImageSelector } from 'features/gallery/store/gallerySelectors';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
interface HoverableImageProps { interface HoverableImageProps {
image: InvokeAI.Image; image: InvokeAI.Image;
@ -55,23 +53,16 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const [isHovered, setIsHovered] = useState<boolean>(false); const [isHovered, setIsHovered] = useState<boolean>(false);
const toast = useToast(); const toast = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const setBothPrompts = useSetBothPrompts();
const handleMouseOver = () => setIsHovered(true); const handleMouseOver = () => setIsHovered(true);
const handleMouseOut = () => setIsHovered(false); const handleMouseOut = () => setIsHovered(false);
const handleUsePrompt = () => { const handleUsePrompt = () => {
if (image.metadata) { if (image.metadata?.image?.prompt) {
const [prompt, negativePrompt] = getPromptAndNegative( setBothPrompts(image.metadata?.image?.prompt);
image.metadata?.image?.prompt
);
prompt && dispatch(setPrompt(prompt));
negativePrompt
? dispatch(setNegativePrompt(negativePrompt))
: dispatch(setNegativePrompt(''));
} }
toast({ toast({

View File

@ -12,6 +12,7 @@ import * as InvokeAI from 'app/invokeai';
import { useAppDispatch } from 'app/storeHooks'; import { useAppDispatch } from 'app/storeHooks';
import promptToString from 'common/util/promptToString'; import promptToString from 'common/util/promptToString';
import { seedWeightsToString } from 'common/util/seedWeightPairs'; import { seedWeightsToString } from 'common/util/seedWeightPairs';
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
import { import {
setCfgScale, setCfgScale,
setHeight, setHeight,
@ -19,7 +20,6 @@ import {
setInitialImage, setInitialImage,
setMaskPath, setMaskPath,
setPerlin, setPerlin,
setPrompt,
setSampler, setSampler,
setSeamless, setSeamless,
setSeed, setSeed,
@ -129,6 +129,8 @@ const ImageMetadataViewer = memo(
({ image, styleClass }: ImageMetadataViewerProps) => { ({ image, styleClass }: ImageMetadataViewerProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const setBothPrompts = useSetBothPrompts();
useHotkeys('esc', () => { useHotkeys('esc', () => {
dispatch(setShouldShowImageDetails(false)); dispatch(setShouldShowImageDetails(false));
}); });
@ -152,7 +154,6 @@ const ImageMetadataViewer = memo(
seed, seed,
steps, steps,
strength, strength,
denoise_str,
threshold, threshold,
type, type,
variations, variations,
@ -189,8 +190,10 @@ const ImageMetadataViewer = memo(
<MetadataItem <MetadataItem
label="Prompt" label="Prompt"
labelPosition="top" labelPosition="top"
value={promptToString(prompt)} value={
onClick={() => dispatch(setPrompt(prompt))} typeof prompt === 'string' ? prompt : promptToString(prompt)
}
onClick={() => setBothPrompts(prompt)}
/> />
)} )}
{seed !== undefined && ( {seed !== undefined && (

View File

@ -0,0 +1,26 @@
import { getPromptAndNegative } from 'common/util/getPromptAndNegative';
import * as InvokeAI from 'app/invokeai';
import promptToString from 'common/util/promptToString';
import { useAppDispatch } from 'app/storeHooks';
import { setNegativePrompt, setPrompt } from '../store/generationSlice';
// TECHDEBT: We have two metadata prompt formats and need to handle recalling either of them.
// This hook provides a function to do that.
const useSetBothPrompts = () => {
const dispatch = useAppDispatch();
return (inputPrompt: InvokeAI.Prompt) => {
const promptString =
typeof inputPrompt === 'string'
? inputPrompt
: promptToString(inputPrompt);
const [prompt, negativePrompt] = getPromptAndNegative(promptString);
dispatch(setPrompt(prompt));
dispatch(setNegativePrompt(negativePrompt));
};
};
export default useSetBothPrompts;