mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
adapt embedding popover to work for trigger phrases also
This commit is contained in:
parent
ef474a3196
commit
6eb4c1ccb6
@ -304,6 +304,12 @@
|
|||||||
"method": "High Resolution Fix Method"
|
"method": "High Resolution Fix Method"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"prompt": {
|
||||||
|
"addPromptTrigger": "Add Prompt Trigger",
|
||||||
|
"compatibleEmbeddings": "Compatible Embeddings",
|
||||||
|
"noPromptTriggers": "No triggers available",
|
||||||
|
"noMatchingTriggers": "No matching triggers"
|
||||||
|
},
|
||||||
"embedding": {
|
"embedding": {
|
||||||
"addEmbedding": "Add Embedding",
|
"addEmbedding": "Add Embedding",
|
||||||
"incompatibleModel": "Incompatible base model:",
|
"incompatibleModel": "Incompatible base model:",
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
|
||||||
|
|
||||||
import { EmbeddingSelect } from './EmbeddingSelect';
|
|
||||||
import type { EmbeddingSelectProps } from './types';
|
|
||||||
|
|
||||||
const meta: Meta<typeof EmbeddingSelect> = {
|
|
||||||
title: 'Feature/Prompt/EmbeddingSelect',
|
|
||||||
tags: ['autodocs'],
|
|
||||||
component: EmbeddingSelect,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof EmbeddingSelect>;
|
|
||||||
|
|
||||||
const Component = (props: EmbeddingSelectProps) => {
|
|
||||||
return <EmbeddingSelect {...props}>Invoke</EmbeddingSelect>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
render: Component,
|
|
||||||
};
|
|
@ -1,67 +0,0 @@
|
|||||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
|
||||||
import { Combobox, FormControl } from '@invoke-ai/ui-library';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
|
||||||
import type { EmbeddingSelectProps } from 'features/embedding/types';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
|
|
||||||
import type { TextualInversionModelConfig } from 'services/api/types';
|
|
||||||
|
|
||||||
const noOptionsMessage = () => t('embedding.noMatchingEmbedding');
|
|
||||||
|
|
||||||
export const EmbeddingSelect = memo(({ onSelect, onClose }: EmbeddingSelectProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
|
|
||||||
|
|
||||||
const getIsDisabled = useCallback(
|
|
||||||
(embedding: TextualInversionModelConfig): boolean => {
|
|
||||||
const isCompatible = currentBaseModel === embedding.base;
|
|
||||||
const hasMainModel = Boolean(currentBaseModel);
|
|
||||||
return !hasMainModel || !isCompatible;
|
|
||||||
},
|
|
||||||
[currentBaseModel]
|
|
||||||
);
|
|
||||||
const { data, isLoading } = useGetTextualInversionModelsQuery();
|
|
||||||
|
|
||||||
const _onChange = useCallback(
|
|
||||||
(embedding: TextualInversionModelConfig | null) => {
|
|
||||||
if (!embedding) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSelect(embedding.name);
|
|
||||||
},
|
|
||||||
[onSelect]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { options, onChange } = useGroupedModelCombobox({
|
|
||||||
modelEntities: data,
|
|
||||||
getIsDisabled,
|
|
||||||
onChange: _onChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormControl>
|
|
||||||
<Combobox
|
|
||||||
placeholder={isLoading ? t('common.loading') : t('embedding.addEmbedding')}
|
|
||||||
defaultMenuIsOpen
|
|
||||||
autoFocus
|
|
||||||
value={null}
|
|
||||||
options={options}
|
|
||||||
noOptionsMessage={noOptionsMessage}
|
|
||||||
onChange={onChange}
|
|
||||||
onMenuClose={onClose}
|
|
||||||
data-testid="add-embedding"
|
|
||||||
sx={selectStyles}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
EmbeddingSelect.displayName = 'EmbeddingSelect';
|
|
||||||
|
|
||||||
const selectStyles: ChakraProps['sx'] = {
|
|
||||||
w: 'full',
|
|
||||||
};
|
|
@ -10,13 +10,11 @@ export const ModelMetadata = () => {
|
|||||||
const { data: metadata } = useGetModelMetadataQuery(selectedModelKey ?? skipToken);
|
const { data: metadata } = useGetModelMetadataQuery(selectedModelKey ?? skipToken);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Flex flexDir="column" height="full" gap="3">
|
||||||
<Flex flexDir="column" height="full" gap="3">
|
<Box layerStyle="second" borderRadius="base" p={3}>
|
||||||
<Box layerStyle="second" borderRadius="base" p={3}>
|
<TriggerPhrases />
|
||||||
<TriggerPhrases />
|
</Box>
|
||||||
</Box>
|
<DataViewer label="metadata" data={metadata || {}} />
|
||||||
<DataViewer label="metadata" data={metadata || {}} />
|
</Flex>
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Box, Textarea } from '@invoke-ai/ui-library';
|
import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
|
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||||
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
|
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||||
import { usePrompt } from 'features/embedding/usePrompt';
|
import { usePrompt } from 'features/prompt/usePrompt';
|
||||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||||
import { setNegativePrompt } from 'features/parameters/store/generationSlice';
|
import { setNegativePrompt } from 'features/parameters/store/generationSlice';
|
||||||
import { memo, useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
@ -19,19 +19,14 @@ export const ParamNegativePrompt = memo(() => {
|
|||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
const { onChange, isOpen, onClose, onOpen, onSelectEmbedding, onKeyDown } = usePrompt({
|
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({
|
||||||
prompt,
|
prompt,
|
||||||
textareaRef,
|
textareaRef,
|
||||||
onChange: _onChange,
|
onChange: _onChange,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EmbeddingPopover
|
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={onClose}
|
|
||||||
onSelect={onSelectEmbedding}
|
|
||||||
width={textareaRef.current?.clientWidth}
|
|
||||||
>
|
|
||||||
<Box pos="relative">
|
<Box pos="relative">
|
||||||
<Textarea
|
<Textarea
|
||||||
id="negativePrompt"
|
id="negativePrompt"
|
||||||
@ -45,10 +40,10 @@ export const ParamNegativePrompt = memo(() => {
|
|||||||
variant="darkFilled"
|
variant="darkFilled"
|
||||||
/>
|
/>
|
||||||
<PromptOverlayButtonWrapper>
|
<PromptOverlayButtonWrapper>
|
||||||
<AddEmbeddingButton isOpen={isOpen} onOpen={onOpen} />
|
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||||
</PromptOverlayButtonWrapper>
|
</PromptOverlayButtonWrapper>
|
||||||
</Box>
|
</Box>
|
||||||
</EmbeddingPopover>
|
</PromptPopover>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Box, Textarea } from '@invoke-ai/ui-library';
|
import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton';
|
import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton';
|
||||||
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
|
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||||
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
|
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||||
import { usePrompt } from 'features/embedding/usePrompt';
|
import { usePrompt } from 'features/prompt/usePrompt';
|
||||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||||
import { setPositivePrompt } from 'features/parameters/store/generationSlice';
|
import { setPositivePrompt } from 'features/parameters/store/generationSlice';
|
||||||
import { SDXLConcatButton } from 'features/sdxl/components/SDXLPrompts/SDXLConcatButton';
|
import { SDXLConcatButton } from 'features/sdxl/components/SDXLPrompts/SDXLConcatButton';
|
||||||
@ -25,7 +25,7 @@ export const ParamPositivePrompt = memo(() => {
|
|||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
const { onChange, isOpen, onClose, onOpen, onSelectEmbedding, onKeyDown, onFocus } = usePrompt({
|
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown, onFocus } = usePrompt({
|
||||||
prompt,
|
prompt,
|
||||||
textareaRef: textareaRef,
|
textareaRef: textareaRef,
|
||||||
onChange: handleChange,
|
onChange: handleChange,
|
||||||
@ -42,12 +42,7 @@ export const ParamPositivePrompt = memo(() => {
|
|||||||
useHotkeys('alt+a', focus, []);
|
useHotkeys('alt+a', focus, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EmbeddingPopover
|
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={onClose}
|
|
||||||
onSelect={onSelectEmbedding}
|
|
||||||
width={textareaRef.current?.clientWidth}
|
|
||||||
>
|
|
||||||
<Box pos="relative">
|
<Box pos="relative">
|
||||||
<Textarea
|
<Textarea
|
||||||
id="prompt"
|
id="prompt"
|
||||||
@ -56,17 +51,17 @@ export const ParamPositivePrompt = memo(() => {
|
|||||||
value={prompt}
|
value={prompt}
|
||||||
placeholder={t('parameters.positivePromptPlaceholder')}
|
placeholder={t('parameters.positivePromptPlaceholder')}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
minH={28}
|
minH={32}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
variant="darkFilled"
|
variant="darkFilled"
|
||||||
/>
|
/>
|
||||||
<PromptOverlayButtonWrapper>
|
<PromptOverlayButtonWrapper>
|
||||||
<AddEmbeddingButton isOpen={isOpen} onOpen={onOpen} />
|
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||||
{baseModel === 'sdxl' && <SDXLConcatButton />}
|
{baseModel === 'sdxl' && <SDXLConcatButton />}
|
||||||
<ShowDynamicPromptsPreviewButton />
|
<ShowDynamicPromptsPreviewButton />
|
||||||
</PromptOverlayButtonWrapper>
|
</PromptOverlayButtonWrapper>
|
||||||
</Box>
|
</Box>
|
||||||
</EmbeddingPopover>
|
</PromptPopover>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,15 +8,15 @@ type Props = {
|
|||||||
onOpen: () => void;
|
onOpen: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddEmbeddingButton = memo((props: Props) => {
|
export const AddPromptTriggerButton = memo((props: Props) => {
|
||||||
const { onOpen, isOpen } = props;
|
const { onOpen, isOpen } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Tooltip label={t('embedding.addEmbedding')}>
|
<Tooltip label={t('prompt.addPromptTrigger')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="promptOverlay"
|
variant="promptOverlay"
|
||||||
isDisabled={isOpen}
|
isDisabled={isOpen}
|
||||||
aria-label={t('embedding.addEmbedding')}
|
aria-label={t('prompt.addPromptTrigger')}
|
||||||
icon={<PiCodeBold />}
|
icon={<PiCodeBold />}
|
||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
/>
|
/>
|
||||||
@ -24,4 +24,4 @@ export const AddEmbeddingButton = memo((props: Props) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddEmbeddingButton.displayName = 'AddEmbeddingButton';
|
AddPromptTriggerButton.displayName = 'AddPromptTriggerButton';
|
@ -1,9 +1,9 @@
|
|||||||
import { Popover, PopoverAnchor, PopoverBody, PopoverContent } from '@invoke-ai/ui-library';
|
import { Popover, PopoverAnchor, PopoverBody, PopoverContent } from '@invoke-ai/ui-library';
|
||||||
import { EmbeddingSelect } from 'features/embedding/EmbeddingSelect';
|
import { PromptTriggerSelect } from 'features/prompt/PromptTriggerSelect';
|
||||||
import type { EmbeddingPopoverProps } from 'features/embedding/types';
|
import type { PromptPopoverProps } from 'features/prompt/types';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
export const EmbeddingPopover = memo((props: EmbeddingPopoverProps) => {
|
export const PromptPopover = memo((props: PromptPopoverProps) => {
|
||||||
const { onSelect, isOpen, onClose, width, children } = props;
|
const { onSelect, isOpen, onClose, width, children } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -14,7 +14,7 @@ export const EmbeddingPopover = memo((props: EmbeddingPopoverProps) => {
|
|||||||
openDelay={0}
|
openDelay={0}
|
||||||
closeDelay={0}
|
closeDelay={0}
|
||||||
closeOnBlur={true}
|
closeOnBlur={true}
|
||||||
returnFocusOnClose={true}
|
returnFocusOnClose={false}
|
||||||
isLazy
|
isLazy
|
||||||
>
|
>
|
||||||
<PopoverAnchor>{children}</PopoverAnchor>
|
<PopoverAnchor>{children}</PopoverAnchor>
|
||||||
@ -27,11 +27,11 @@ export const EmbeddingPopover = memo((props: EmbeddingPopoverProps) => {
|
|||||||
borderStyle="solid"
|
borderStyle="solid"
|
||||||
>
|
>
|
||||||
<PopoverBody p={0} width={`calc(${width}px - 0.25rem)`}>
|
<PopoverBody p={0} width={`calc(${width}px - 0.25rem)`}>
|
||||||
<EmbeddingSelect onClose={onClose} onSelect={onSelect} />
|
<PromptTriggerSelect onClose={onClose} onSelect={onSelect} />
|
||||||
</PopoverBody>
|
</PopoverBody>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
EmbeddingPopover.displayName = 'EmbeddingPopover';
|
PromptPopover.displayName = 'PromptPopover';
|
@ -0,0 +1,21 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { PromptTriggerSelect } from './PromptTriggerSelect';
|
||||||
|
import type { PromptTriggerSelectProps } from './types';
|
||||||
|
|
||||||
|
const meta: Meta<typeof PromptTriggerSelect> = {
|
||||||
|
title: 'Feature/Prompt/PromptTriggerSelect',
|
||||||
|
tags: ['autodocs'],
|
||||||
|
component: PromptTriggerSelect,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof PromptTriggerSelect>;
|
||||||
|
|
||||||
|
const Component = (props: PromptTriggerSelectProps) => {
|
||||||
|
return <PromptTriggerSelect {...props}>Invoke</PromptTriggerSelect>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: Component,
|
||||||
|
};
|
@ -0,0 +1,87 @@
|
|||||||
|
import type { ChakraProps, ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||||
|
import { Combobox, FormControl } from '@invoke-ai/ui-library';
|
||||||
|
import { skipToken } from '@reduxjs/toolkit/query';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import type { PromptTriggerSelectProps } from 'features/prompt/types';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import { map } from 'lodash-es';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useGetModelMetadataQuery, useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
|
||||||
|
|
||||||
|
const noOptionsMessage = () => t('prompt.noMatchingTriggers');
|
||||||
|
|
||||||
|
export const PromptTriggerSelect = memo(({ onSelect, onClose }: PromptTriggerSelectProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
|
||||||
|
const currentModelKey = useAppSelector((s) => s.generation.model?.key);
|
||||||
|
|
||||||
|
const { data, isLoading } = useGetTextualInversionModelsQuery();
|
||||||
|
const { data: metadata } = useGetModelMetadataQuery(currentModelKey ?? skipToken);
|
||||||
|
|
||||||
|
const _onChange = useCallback<ComboboxOnChange>(
|
||||||
|
(v) => {
|
||||||
|
if (!v) {
|
||||||
|
onSelect('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect(v.value);
|
||||||
|
},
|
||||||
|
[onSelect]
|
||||||
|
);
|
||||||
|
|
||||||
|
const embeddingOptions = useMemo(() => {
|
||||||
|
if (!data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const compatibleEmbeddingsArray = map(data.entities).filter((model) => model.base === currentBaseModel);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('prompt.compatibleEmbeddings'),
|
||||||
|
options: compatibleEmbeddingsArray.map((model) => ({ label: model.name, value: `<${model.name}>` })),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [data, currentBaseModel]);
|
||||||
|
|
||||||
|
const options = useMemo(() => {
|
||||||
|
if (!metadata || !metadata.trigger_phrases) {
|
||||||
|
return [...embeddingOptions];
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadataOptions = [
|
||||||
|
{
|
||||||
|
label: t('modelManager.triggerPhrases'),
|
||||||
|
options: metadata.trigger_phrases.map((phrase) => ({ label: phrase, value: phrase })),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return [...metadataOptions, ...embeddingOptions];
|
||||||
|
}, [embeddingOptions, metadata]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
placeholder={isLoading ? t('common.loading') : t('prompt.addPromptTrigger')}
|
||||||
|
defaultMenuIsOpen
|
||||||
|
autoFocus
|
||||||
|
value={null}
|
||||||
|
options={options}
|
||||||
|
noOptionsMessage={noOptionsMessage}
|
||||||
|
onChange={_onChange}
|
||||||
|
onMenuClose={onClose}
|
||||||
|
data-testid="add-prompt-trigger"
|
||||||
|
sx={selectStyles}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
PromptTriggerSelect.displayName = 'PromptTriggerSelect';
|
||||||
|
|
||||||
|
const selectStyles: ChakraProps['sx'] = {
|
||||||
|
w: 'full',
|
||||||
|
};
|
@ -1,12 +1,13 @@
|
|||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
export type EmbeddingSelectProps = {
|
export type PromptTriggerSelectProps = {
|
||||||
onSelect: (v: string) => void;
|
onSelect: (v: string) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EmbeddingPopoverProps = PropsWithChildren &
|
export type PromptPopoverProps = PropsWithChildren &
|
||||||
EmbeddingSelectProps & {
|
PromptTriggerSelectProps & {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
|
|
||||||
};
|
};
|
@ -4,13 +4,13 @@ import type { ChangeEventHandler, KeyboardEventHandler, RefObject } from 'react'
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { flushSync } from 'react-dom';
|
import { flushSync } from 'react-dom';
|
||||||
|
|
||||||
type UseInsertEmbeddingArg = {
|
type UseInsertTriggerArg = {
|
||||||
prompt: string;
|
prompt: string;
|
||||||
textareaRef: RefObject<HTMLTextAreaElement>;
|
textareaRef: RefObject<HTMLTextAreaElement>;
|
||||||
onChange: (v: string) => void;
|
onChange: (v: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInsertEmbeddingArg) => {
|
export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInsertTriggerArg) => {
|
||||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||||
|
|
||||||
const onChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
|
const onChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
|
||||||
@ -20,13 +20,14 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
|
|||||||
[_onChange]
|
[_onChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
const insertEmbedding = useCallback(
|
const insertTrigger = useCallback(
|
||||||
(v: string) => {
|
(v: string) => {
|
||||||
|
console.log({ textareaRef })
|
||||||
if (!textareaRef.current) {
|
if (!textareaRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is where we insert the TI trigger
|
// this is where we insert the trigger
|
||||||
const caret = textareaRef.current.selectionStart;
|
const caret = textareaRef.current.selectionStart;
|
||||||
|
|
||||||
if (isNil(caret)) {
|
if (isNil(caret)) {
|
||||||
@ -35,46 +36,48 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
|
|||||||
|
|
||||||
let newPrompt = prompt.slice(0, caret);
|
let newPrompt = prompt.slice(0, caret);
|
||||||
|
|
||||||
if (newPrompt[newPrompt.length - 1] !== '<') {
|
newPrompt += `${v}`;
|
||||||
newPrompt += '<';
|
|
||||||
}
|
|
||||||
|
|
||||||
newPrompt += `${v}>`;
|
// we insert the cursor after the end of trigger
|
||||||
|
|
||||||
// we insert the cursor after the `>`
|
|
||||||
const finalCaretPos = newPrompt.length;
|
const finalCaretPos = newPrompt.length;
|
||||||
|
|
||||||
newPrompt += prompt.slice(caret);
|
newPrompt += prompt.slice(caret);
|
||||||
|
console.log({ newPrompt })
|
||||||
|
|
||||||
// must flush dom updates else selection gets reset
|
// must flush dom updates else selection gets reset
|
||||||
flushSync(() => {
|
flushSync(() => {
|
||||||
_onChange(newPrompt);
|
_onChange(newPrompt);
|
||||||
});
|
});
|
||||||
|
|
||||||
// set the caret position to just after the TI trigger
|
// set the cursor position to just after the trigger
|
||||||
textareaRef.current.selectionStart = finalCaretPos;
|
textareaRef.current.selectionStart = finalCaretPos;
|
||||||
textareaRef.current.selectionEnd = finalCaretPos;
|
textareaRef.current.selectionEnd = finalCaretPos;
|
||||||
},
|
},
|
||||||
[textareaRef, _onChange, prompt]
|
[textareaRef, _onChange, prompt]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const onFocus = useCallback(() => {
|
const onFocus = useCallback(() => {
|
||||||
|
console.log("focus")
|
||||||
|
console.log(textareaRef.current)
|
||||||
textareaRef.current?.focus();
|
textareaRef.current?.focus();
|
||||||
}, [textareaRef]);
|
}, [textareaRef]);
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClosePopover = useCallback(() => {
|
||||||
onClose();
|
onClose();
|
||||||
onFocus();
|
onFocus();
|
||||||
}, [onFocus, onClose]);
|
}, [onFocus, onClose]);
|
||||||
|
|
||||||
const onSelectEmbedding = useCallback(
|
|
||||||
|
const onSelect = useCallback(
|
||||||
(v: string) => {
|
(v: string) => {
|
||||||
insertEmbedding(v);
|
insertTrigger(v);
|
||||||
handleClose();
|
handleClosePopover();
|
||||||
},
|
},
|
||||||
[handleClose, insertEmbedding]
|
[handleClosePopover, insertTrigger]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const onKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = useCallback(
|
const onKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
if (e.key === '<') {
|
if (e.key === '<') {
|
||||||
@ -90,7 +93,7 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
|
|||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
onOpen,
|
onOpen,
|
||||||
onSelectEmbedding,
|
onSelect,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
onFocus,
|
onFocus,
|
||||||
};
|
};
|
@ -1,8 +1,8 @@
|
|||||||
import { Box, Textarea } from '@invoke-ai/ui-library';
|
import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
|
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||||
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
|
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||||
import { usePrompt } from 'features/embedding/usePrompt';
|
import { usePrompt } from 'features/prompt/usePrompt';
|
||||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||||
import { setNegativeStylePromptSDXL } from 'features/sdxl/store/sdxlSlice';
|
import { setNegativeStylePromptSDXL } from 'features/sdxl/store/sdxlSlice';
|
||||||
import { memo, useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
@ -20,7 +20,7 @@ export const ParamSDXLNegativeStylePrompt = memo(() => {
|
|||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
const { onChange, isOpen, onClose, onOpen, onSelectEmbedding, onKeyDown, onFocus } = usePrompt({
|
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown, onFocus } = usePrompt({
|
||||||
prompt,
|
prompt,
|
||||||
textareaRef: textareaRef,
|
textareaRef: textareaRef,
|
||||||
onChange: handleChange,
|
onChange: handleChange,
|
||||||
@ -29,12 +29,7 @@ export const ParamSDXLNegativeStylePrompt = memo(() => {
|
|||||||
useHotkeys('alt+a', onFocus, []);
|
useHotkeys('alt+a', onFocus, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EmbeddingPopover
|
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={onClose}
|
|
||||||
onSelect={onSelectEmbedding}
|
|
||||||
width={textareaRef.current?.clientWidth}
|
|
||||||
>
|
|
||||||
<Box pos="relative">
|
<Box pos="relative">
|
||||||
<Textarea
|
<Textarea
|
||||||
id="prompt"
|
id="prompt"
|
||||||
@ -48,10 +43,10 @@ export const ParamSDXLNegativeStylePrompt = memo(() => {
|
|||||||
variant="darkFilled"
|
variant="darkFilled"
|
||||||
/>
|
/>
|
||||||
<PromptOverlayButtonWrapper>
|
<PromptOverlayButtonWrapper>
|
||||||
<AddEmbeddingButton isOpen={isOpen} onOpen={onOpen} />
|
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||||
</PromptOverlayButtonWrapper>
|
</PromptOverlayButtonWrapper>
|
||||||
</Box>
|
</Box>
|
||||||
</EmbeddingPopover>
|
</PromptPopover>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Box, Textarea } from '@invoke-ai/ui-library';
|
import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
|
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||||
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
|
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||||
import { usePrompt } from 'features/embedding/usePrompt';
|
import { usePrompt } from 'features/prompt/usePrompt';
|
||||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||||
import { setPositiveStylePromptSDXL } from 'features/sdxl/store/sdxlSlice';
|
import { setPositiveStylePromptSDXL } from 'features/sdxl/store/sdxlSlice';
|
||||||
import { memo, useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
@ -19,19 +19,14 @@ export const ParamSDXLPositiveStylePrompt = memo(() => {
|
|||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
const { onChange, isOpen, onClose, onOpen, onSelectEmbedding, onKeyDown } = usePrompt({
|
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({
|
||||||
prompt,
|
prompt,
|
||||||
textareaRef: textareaRef,
|
textareaRef: textareaRef,
|
||||||
onChange: handleChange,
|
onChange: handleChange,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EmbeddingPopover
|
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={onClose}
|
|
||||||
onSelect={onSelectEmbedding}
|
|
||||||
width={textareaRef.current?.clientWidth}
|
|
||||||
>
|
|
||||||
<Box pos="relative">
|
<Box pos="relative">
|
||||||
<Textarea
|
<Textarea
|
||||||
id="prompt"
|
id="prompt"
|
||||||
@ -45,10 +40,10 @@ export const ParamSDXLPositiveStylePrompt = memo(() => {
|
|||||||
variant="darkFilled"
|
variant="darkFilled"
|
||||||
/>
|
/>
|
||||||
<PromptOverlayButtonWrapper>
|
<PromptOverlayButtonWrapper>
|
||||||
<AddEmbeddingButton isOpen={isOpen} onOpen={onOpen} />
|
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||||
</PromptOverlayButtonWrapper>
|
</PromptOverlayButtonWrapper>
|
||||||
</Box>
|
</Box>
|
||||||
</EmbeddingPopover>
|
</PromptPopover>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user