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:
psychedelicious 2023-07-06 14:56:37 +10:00
parent 2415dc1235
commit fbd6b25b4d
3 changed files with 79 additions and 31 deletions

View File

@ -68,17 +68,26 @@ const ParamEmbeddingPopover = (props: Props) => {
return (
<Popover
initialFocusRef={inputRef}
returnFocusOnClose={true}
isOpen={isOpen}
onClose={onClose}
placement="bottom"
openDelay={0}
closeDelay={0}
closeOnBlur={true}
returnFocusOnClose={true}
>
<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
sx={{ p: 1, w: `calc(${PARAMETERS_PANEL_WIDTH} - 2rem )` }}
sx={{ p: 0.5, w: `calc(${PARAMETERS_PANEL_WIDTH} - 2rem )` }}
>
<IAIMantineMultiSelect
inputRef={inputRef}

View File

@ -6,6 +6,7 @@ import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
import { setNegativePrompt } from 'features/parameters/store/generationSlice';
import { ChangeEvent, KeyboardEvent, useCallback, useRef } from 'react';
import { flushSync } from 'react-dom';
import { useTranslation } from 'react-i18next';
const ParamNegativeConditioning = () => {
@ -32,9 +33,14 @@ const ParamNegativeConditioning = () => {
[onOpen]
);
const handleSelect = useCallback(
const handleSelectEmbedding = useCallback(
(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) {
return;
@ -47,11 +53,22 @@ const ParamNegativeConditioning = () => {
}
newPrompt += `${v}>`;
// we insert the cursor after the `>`
const finalCaretPos = newPrompt.length;
newPrompt += negativePrompt.slice(caret);
dispatch(setNegativePrompt(newPrompt));
// must flush dom updates else selection gets reset
flushSync(() => {
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 (
@ -59,7 +76,7 @@ const ParamNegativeConditioning = () => {
<ParamEmbeddingPopover
isOpen={isOpen}
onClose={onClose}
onSelect={handleSelect}
onSelect={handleSelectEmbedding}
>
<IAITextarea
id="negativePrompt"

View File

@ -17,6 +17,7 @@ import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
import { isEqual } from 'lodash-es';
import { flushSync } from 'react-dom';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
@ -45,7 +46,6 @@ const ParamPositiveConditioning = () => {
const promptRef = useRef<HTMLTextAreaElement>(null);
const { isOpen, onClose, onOpen } = useDisclosure();
const { t } = useTranslation();
const handleChangePrompt = useCallback(
(e: ChangeEvent<HTMLTextAreaElement>) => {
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(
(e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && e.shiftKey === false && isReady) {
@ -75,27 +114,10 @@ const ParamPositiveConditioning = () => {
[isReady, dispatch, activeTabName, onOpen]
);
const handleSelect = useCallback(
(v: string) => {
const caret = promptRef.current?.selectionStart;
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]
);
// const handleSelect = (e: MouseEvent<HTMLTextAreaElement>) => {
// const target = e.target as HTMLTextAreaElement;
// setCaret({ start: target.selectionStart, end: target.selectionEnd });
// };
return (
<Box>
@ -103,7 +125,7 @@ const ParamPositiveConditioning = () => {
<ParamEmbeddingPopover
isOpen={isOpen}
onClose={onClose}
onSelect={handleSelect}
onSelect={handleSelectEmbedding}
>
<IAITextarea
id="prompt"