mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): improve ux on TI autcomplete
- cursor reinserts at the end of the trigger - `enter` closes the select - popover styling
This commit is contained in:
@ -68,17 +68,26 @@ const ParamEmbeddingPopover = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
initialFocusRef={inputRef}
|
initialFocusRef={inputRef}
|
||||||
returnFocusOnClose={true}
|
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
openDelay={0}
|
openDelay={0}
|
||||||
closeDelay={0}
|
closeDelay={0}
|
||||||
|
closeOnBlur={true}
|
||||||
|
returnFocusOnClose={true}
|
||||||
>
|
>
|
||||||
<PopoverTrigger>{children}</PopoverTrigger>
|
<PopoverTrigger>{children}</PopoverTrigger>
|
||||||
<PopoverContent sx={{ p: 0, top: -1, shadow: 'dark-lg' }}>
|
<PopoverContent
|
||||||
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
top: -1,
|
||||||
|
shadow: 'dark-lg',
|
||||||
|
bg: 'accent.300',
|
||||||
|
_dark: { bg: 'accent.400' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
<PopoverBody
|
<PopoverBody
|
||||||
sx={{ p: 1, w: `calc(${PARAMETERS_PANEL_WIDTH} - 2rem )` }}
|
sx={{ p: 0.5, w: `calc(${PARAMETERS_PANEL_WIDTH} - 2rem )` }}
|
||||||
>
|
>
|
||||||
<IAIMantineMultiSelect
|
<IAIMantineMultiSelect
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
|
@ -6,6 +6,7 @@ import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton
|
|||||||
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
|
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
|
||||||
import { setNegativePrompt } from 'features/parameters/store/generationSlice';
|
import { setNegativePrompt } from 'features/parameters/store/generationSlice';
|
||||||
import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react';
|
import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const ParamNegativeConditioning = () => {
|
const ParamNegativeConditioning = () => {
|
||||||
@ -32,9 +33,14 @@ const ParamNegativeConditioning = () => {
|
|||||||
[onOpen]
|
[onOpen]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelectEmbedding = useCallback(
|
||||||
(v: string) => {
|
(v: string) => {
|
||||||
const caret = promptRef.current?.selectionStart;
|
if (!promptRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is where we insert the TI trigger
|
||||||
|
const caret = promptRef.current.selectionStart;
|
||||||
|
|
||||||
if (caret === undefined) {
|
if (caret === undefined) {
|
||||||
return;
|
return;
|
||||||
@ -47,11 +53,22 @@ const ParamNegativeConditioning = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newPrompt += `${v}>`;
|
newPrompt += `${v}>`;
|
||||||
|
|
||||||
|
// we insert the cursor after the `>`
|
||||||
|
const finalCaretPos = newPrompt.length;
|
||||||
|
|
||||||
newPrompt += negativePrompt.slice(caret);
|
newPrompt += negativePrompt.slice(caret);
|
||||||
|
|
||||||
|
// must flush dom updates else selection gets reset
|
||||||
|
flushSync(() => {
|
||||||
dispatch(setNegativePrompt(newPrompt));
|
dispatch(setNegativePrompt(newPrompt));
|
||||||
|
});
|
||||||
|
|
||||||
|
// set the caret position to just after the TI trigger promptRef.current.selectionStart = finalCaretPos;
|
||||||
|
promptRef.current.selectionEnd = finalCaretPos;
|
||||||
|
onClose();
|
||||||
},
|
},
|
||||||
[dispatch, negativePrompt]
|
[dispatch, onClose, negativePrompt]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -59,7 +76,7 @@ const ParamNegativeConditioning = () => {
|
|||||||
<ParamEmbeddingPopover
|
<ParamEmbeddingPopover
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelectEmbedding}
|
||||||
>
|
>
|
||||||
<IAITextarea
|
<IAITextarea
|
||||||
id="negativePrompt"
|
id="negativePrompt"
|
||||||
|
@ -17,6 +17,7 @@ import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
|
|||||||
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
|
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
|
||||||
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
|
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -45,7 +46,6 @@ const ParamPositiveConditioning = () => {
|
|||||||
const promptRef = useRef<HTMLTextAreaElement>(null);
|
const promptRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChangePrompt = useCallback(
|
const handleChangePrompt = useCallback(
|
||||||
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
dispatch(setPositivePrompt(e.target.value));
|
dispatch(setPositivePrompt(e.target.value));
|
||||||
@ -61,6 +61,45 @@ const ParamPositiveConditioning = () => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleSelectEmbedding = useCallback(
|
||||||
|
(v: string) => {
|
||||||
|
if (!promptRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is where we insert the TI trigger
|
||||||
|
const caret = promptRef.current.selectionStart;
|
||||||
|
|
||||||
|
if (caret === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newPrompt = prompt.slice(0, caret);
|
||||||
|
|
||||||
|
if (newPrompt[newPrompt.length - 1] !== '<') {
|
||||||
|
newPrompt += '<';
|
||||||
|
}
|
||||||
|
|
||||||
|
newPrompt += `${v}>`;
|
||||||
|
|
||||||
|
// we insert the cursor after the `>`
|
||||||
|
const finalCaretPos = newPrompt.length;
|
||||||
|
|
||||||
|
newPrompt += prompt.slice(caret);
|
||||||
|
|
||||||
|
// must flush dom updates else selection gets reset
|
||||||
|
flushSync(() => {
|
||||||
|
dispatch(setPositivePrompt(newPrompt));
|
||||||
|
});
|
||||||
|
|
||||||
|
// set the caret position to just after the TI trigger
|
||||||
|
promptRef.current.selectionStart = finalCaretPos;
|
||||||
|
promptRef.current.selectionEnd = finalCaretPos;
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
[dispatch, onClose, prompt]
|
||||||
|
);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: KeyboardEvent<HTMLTextAreaElement>) => {
|
(e: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
if (e.key === 'Enter' && e.shiftKey === false && isReady) {
|
if (e.key === 'Enter' && e.shiftKey === false && isReady) {
|
||||||
@ -75,27 +114,10 @@ const ParamPositiveConditioning = () => {
|
|||||||
[isReady, dispatch, activeTabName, onOpen]
|
[isReady, dispatch, activeTabName, onOpen]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
// const handleSelect = (e: MouseEvent<HTMLTextAreaElement>) => {
|
||||||
(v: string) => {
|
// const target = e.target as HTMLTextAreaElement;
|
||||||
const caret = promptRef.current?.selectionStart;
|
// setCaret({ start: target.selectionStart, end: target.selectionEnd });
|
||||||
|
// };
|
||||||
if (caret === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let newPrompt = prompt.slice(0, caret);
|
|
||||||
|
|
||||||
if (newPrompt[newPrompt.length - 1] !== '<') {
|
|
||||||
newPrompt += '<';
|
|
||||||
}
|
|
||||||
|
|
||||||
newPrompt += `${v}>`;
|
|
||||||
newPrompt += prompt.slice(caret);
|
|
||||||
|
|
||||||
dispatch(setPositivePrompt(newPrompt));
|
|
||||||
},
|
|
||||||
[dispatch, prompt]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@ -103,7 +125,7 @@ const ParamPositiveConditioning = () => {
|
|||||||
<ParamEmbeddingPopover
|
<ParamEmbeddingPopover
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelectEmbedding}
|
||||||
>
|
>
|
||||||
<IAITextarea
|
<IAITextarea
|
||||||
id="prompt"
|
id="prompt"
|
||||||
|
Reference in New Issue
Block a user