diff --git a/invokeai/backend/invoke_ai_web_server.py b/invokeai/backend/invoke_ai_web_server.py index 8bb97a69d5..5695e788ec 100644 --- a/invokeai/backend/invoke_ai_web_server.py +++ b/invokeai/backend/invoke_ai_web_server.py @@ -1383,13 +1383,6 @@ class InvokeAIWebServer: # semantic drift 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 = [] diff --git a/invokeai/frontend/src/app/invokeai.d.ts b/invokeai/frontend/src/app/invokeai.d.ts index afa5fcec3d..e01e414d03 100644 --- a/invokeai/frontend/src/app/invokeai.d.ts +++ b/invokeai/frontend/src/app/invokeai.d.ts @@ -29,7 +29,8 @@ export declare type PromptItem = { weight: number; }; -export declare type Prompt = Array; +// TECHDEBT: We need to retain compatibility with plain prompt strings and the structure Prompt type +export declare type Prompt = Array | string; export declare type SeedWeightPair = { seed: number; diff --git a/invokeai/frontend/src/common/util/getPromptAndNegative.ts b/invokeai/frontend/src/common/util/getPromptAndNegative.ts index 3bcc1dbed3..7400e63bfc 100644 --- a/invokeai/frontend/src/common/util/getPromptAndNegative.ts +++ b/invokeai/frontend/src/common/util/getPromptAndNegative.ts @@ -1,9 +1,11 @@ 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; +export function getPromptAndNegative(inputPrompt: InvokeAI.Prompt) { + let prompt: string = + typeof inputPrompt === 'string' ? inputPrompt : promptToString(inputPrompt); + + let negativePrompt = ''; // Matches all negative prompts, 1st capturing group is the prompt itself const negativePromptRegExp = new RegExp(/\[([^\][]*)]/, 'gi'); diff --git a/invokeai/frontend/src/common/util/promptToString.ts b/invokeai/frontend/src/common/util/promptToString.ts index ef1e7796e6..5121e25eef 100644 --- a/invokeai/frontend/src/common/util/promptToString.ts +++ b/invokeai/frontend/src/common/util/promptToString.ts @@ -1,6 +1,10 @@ import * as InvokeAI from 'app/invokeai'; const promptToString = (prompt: InvokeAI.Prompt): string => { + if (typeof prompt === 'string') { + return prompt; + } + if (prompt.length === 1) { return prompt[0].prompt; } diff --git a/invokeai/frontend/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/src/features/gallery/components/CurrentImageButtons.tsx index 3a9880c2c7..92d6aa6d06 100644 --- a/invokeai/frontend/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/src/features/gallery/components/CurrentImageButtons.tsx @@ -7,7 +7,6 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import IAIButton from 'common/components/IAIButton'; import IAIIconButton from 'common/components/IAIIconButton'; import IAIPopover from 'common/components/IAIPopover'; -import { getPromptAndNegative } from 'common/util/getPromptAndNegative'; import { setDoesCanvasNeedScaling, setInitialCanvasImage, @@ -20,8 +19,6 @@ import UpscaleSettings from 'features/parameters/components/AdvancedParameters/U import { setAllParameters, setInitialImage, - setNegativePrompt, - setPrompt, setSeed, } from 'features/parameters/store/generationSlice'; import { postprocessingSelector } from 'features/parameters/store/postprocessingSelectors'; @@ -53,6 +50,8 @@ import { } from 'react-icons/fa'; import { gallerySelector } from '../store/gallerySelectors'; import DeleteImageModal from './DeleteImageModal'; +import { useCallback } from 'react'; +import useSetBothPrompts from 'features/parameters/hooks/usePrompt'; const currentImageButtonsSelector = createSelector( [ @@ -125,6 +124,7 @@ const CurrentImageButtons = () => { const toast = useToast(); const { t } = useTranslation(); + const setBothPrompts = useSetBothPrompts(); const handleClickUseAsInitialImage = () => { if (!currentImage) return; @@ -253,18 +253,11 @@ const CurrentImageButtons = () => { [currentImage] ); - const handleClickUsePrompt = () => { + const handleClickUsePrompt = useCallback(() => { if (currentImage?.metadata?.image?.prompt) { - const [prompt, negativePrompt] = getPromptAndNegative( - currentImage?.metadata?.image?.prompt - ); - - prompt && dispatch(setPrompt(prompt)); - negativePrompt - ? dispatch(setNegativePrompt(negativePrompt)) - : dispatch(setNegativePrompt('')); + setBothPrompts(currentImage?.metadata?.image?.prompt); } - }; + }, [currentImage?.metadata?.image?.prompt, setBothPrompts]); useHotkeys( 'p', diff --git a/invokeai/frontend/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/src/features/gallery/components/HoverableImage.tsx index 70385ae4a8..e3e136ea5b 100644 --- a/invokeai/frontend/src/features/gallery/components/HoverableImage.tsx +++ b/invokeai/frontend/src/features/gallery/components/HoverableImage.tsx @@ -8,8 +8,6 @@ import { setAllImageToImageParameters, setAllParameters, setInitialImage, - setNegativePrompt, - setPrompt, setSeed, } from 'features/parameters/store/generationSlice'; 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 InvokeAI from 'app/invokeai'; -import { getPromptAndNegative } from 'common/util/getPromptAndNegative'; import { resizeAndScaleCanvas, setInitialCanvasImage, @@ -26,6 +23,7 @@ import { import { hoverableImageSelector } from 'features/gallery/store/gallerySelectors'; import { setActiveTab } from 'features/ui/store/uiSlice'; import { useTranslation } from 'react-i18next'; +import useSetBothPrompts from 'features/parameters/hooks/usePrompt'; interface HoverableImageProps { image: InvokeAI.Image; @@ -55,23 +53,16 @@ const HoverableImage = memo((props: HoverableImageProps) => { const [isHovered, setIsHovered] = useState(false); const toast = useToast(); - const { t } = useTranslation(); + const setBothPrompts = useSetBothPrompts(); const handleMouseOver = () => setIsHovered(true); const handleMouseOut = () => setIsHovered(false); const handleUsePrompt = () => { - if (image.metadata) { - const [prompt, negativePrompt] = getPromptAndNegative( - image.metadata?.image?.prompt - ); - - prompt && dispatch(setPrompt(prompt)); - negativePrompt - ? dispatch(setNegativePrompt(negativePrompt)) - : dispatch(setNegativePrompt('')); + if (image.metadata?.image?.prompt) { + setBothPrompts(image.metadata?.image?.prompt); } toast({ diff --git a/invokeai/frontend/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx b/invokeai/frontend/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx index ebdbe80e91..a06ebc0bc1 100644 --- a/invokeai/frontend/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx +++ b/invokeai/frontend/src/features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer.tsx @@ -12,6 +12,7 @@ import * as InvokeAI from 'app/invokeai'; import { useAppDispatch } from 'app/storeHooks'; import promptToString from 'common/util/promptToString'; import { seedWeightsToString } from 'common/util/seedWeightPairs'; +import useSetBothPrompts from 'features/parameters/hooks/usePrompt'; import { setCfgScale, setHeight, @@ -19,7 +20,6 @@ import { setInitialImage, setMaskPath, setPerlin, - setPrompt, setSampler, setSeamless, setSeed, @@ -129,6 +129,8 @@ const ImageMetadataViewer = memo( ({ image, styleClass }: ImageMetadataViewerProps) => { const dispatch = useAppDispatch(); + const setBothPrompts = useSetBothPrompts(); + useHotkeys('esc', () => { dispatch(setShouldShowImageDetails(false)); }); @@ -152,7 +154,6 @@ const ImageMetadataViewer = memo( seed, steps, strength, - denoise_str, threshold, type, variations, @@ -189,8 +190,10 @@ const ImageMetadataViewer = memo( dispatch(setPrompt(prompt))} + value={ + typeof prompt === 'string' ? prompt : promptToString(prompt) + } + onClick={() => setBothPrompts(prompt)} /> )} {seed !== undefined && ( diff --git a/invokeai/frontend/src/features/parameters/hooks/usePrompt.ts b/invokeai/frontend/src/features/parameters/hooks/usePrompt.ts new file mode 100644 index 0000000000..310f9a9209 --- /dev/null +++ b/invokeai/frontend/src/features/parameters/hooks/usePrompt.ts @@ -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;