Parcourir la source

feat: add search input field (#18409)

tags/1.3.0
allenZhang il y a 6 mois
Parent
révision
94e22ba0fd
Aucun compte lié à l'adresse e-mail de l'auteur

+ 51
- 40
web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx Voir le fichier

@@ -31,6 +31,7 @@ import { useOptions } from './hooks'
import type { PickerBlockMenuOption } from './menu'
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { KEY_ESCAPE_COMMAND } from 'lexical'

type ComponentPickerProps = {
triggerString: string
@@ -118,6 +119,13 @@ const ComponentPicker = ({
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables)
}, [editor, checkForTriggerMatch, triggerString])

const handleClose = useCallback(() => {
ReactDOM.flushSync(() => {
const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' })
editor.dispatchCommand(KEY_ESCAPE_COMMAND, escapeEvent)
})
}, [editor])

const renderMenu = useCallback<MenuRenderFn<PickerBlockMenuOption>>((
anchorElementRef,
{ options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },
@@ -141,51 +149,54 @@ const ComponentPicker = ({
visibility: isPositioned ? 'visible' : 'hidden',
}}
ref={refs.setFloating}
data-testid="component-picker-container"
>
{
options.map((option, index) => (
<Fragment key={option.key}>
{
// Divider
index !== 0 && options.at(index - 1)?.group !== option.group && (
<div className='my-1 h-px w-full -translate-x-1 bg-divider-subtle'></div>
)
}
{option.renderMenuOption({
queryString,
isSelected: selectedIndex === index,
onSelect: () => {
selectOptionAndCleanUp(option)
},
onSetHighlight: () => {
setHighlightedIndex(index)
},
})}
</Fragment>
))
workflowVariableBlock?.show && (
<div className='p-1'>
<VarReferenceVars
searchBoxClassName='mt-1'
vars={workflowVariableOptions}
onChange={(variables: string[]) => {
handleSelectWorkflowVariable(variables)
}}
maxHeightClass='max-h-[34vh]'
isSupportFileVar={isSupportFileVar}
onClose={handleClose}
onBlur={handleClose}
/>
</div>
)
}
{
workflowVariableBlock?.show && (
<>
{
(!!options.length) && (
<div className='my-1 h-px w-full -translate-x-1 bg-divider-subtle'></div>
)
}
<div className='p-1'>
<VarReferenceVars
hideSearch
vars={workflowVariableOptions}
onChange={(variables: string[]) => {
handleSelectWorkflowVariable(variables)
}}
maxHeightClass='max-h-[34vh]'
isSupportFileVar={isSupportFileVar}
/>
</div>
</>
workflowVariableBlock?.show && !!options.length && (
<div className='my-1 h-px w-full -translate-x-1 bg-divider-subtle'></div>
)
}
<div data-testid="options-list">
{
options.map((option, index) => (
<Fragment key={option.key}>
{
// Divider
index !== 0 && options.at(index - 1)?.group !== option.group && (
<div className='my-1 h-px w-full -translate-x-1 bg-divider-subtle'></div>
)
}
{option.renderMenuOption({
queryString,
isSelected: selectedIndex === index,
onSelect: () => {
selectOptionAndCleanUp(option)
},
onSetHighlight: () => {
setHighlightedIndex(index)
},
})}
</Fragment>
))
}
</div>
</div>
</div>,
anchorElementRef.current,
@@ -193,7 +204,7 @@ const ComponentPicker = ({
}
</>
)
}, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable])
}, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable, handleClose, isSupportFileVar])

return (
<LexicalTypeaheadMenuPlugin

+ 10
- 8
web/app/components/base/prompt-editor/plugins/on-blur-or-focus-block.tsx Voir le fichier

@@ -37,14 +37,16 @@ const OnBlurBlock: FC<OnBlurBlockProps> = ({
),
editor.registerCommand(
BLUR_COMMAND,
() => {
ref.current = setTimeout(() => {
editor.dispatchCommand(KEY_ESCAPE_COMMAND, new KeyboardEvent('keydown', { key: 'Escape' }))
}, 200)

if (onBlur)
onBlur()

(event) => {
// Check if the clicked target element is var-search-input
const target = event?.relatedTarget as HTMLElement
if (!target?.classList?.contains('var-search-input')) {
ref.current = setTimeout(() => {
editor.dispatchCommand(KEY_ESCAPE_COMMAND, new KeyboardEvent('keydown', { key: 'Escape' }))
}, 200)
if (onBlur)
onBlur()
}
return true
},
COMMAND_PRIORITY_EDITOR,

+ 15
- 1
web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx Voir le fichier

@@ -258,6 +258,8 @@ type Props = {
onChange: (value: ValueSelector, item: Var) => void
itemWidth?: number
maxHeightClass?: string
onClose?: () => void
onBlur?: () => void
}
const VarReferenceVars: FC<Props> = ({
hideSearch,
@@ -267,10 +269,19 @@ const VarReferenceVars: FC<Props> = ({
onChange,
itemWidth,
maxHeightClass,
onClose,
onBlur,
}) => {
const { t } = useTranslation()
const [searchText, setSearchText] = useState('')

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault()
onClose?.()
}
}

const filteredVars = vars.filter((v) => {
const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.') || v.variable.startsWith('conversation.'))
return children.length > 0
@@ -301,14 +312,17 @@ const VarReferenceVars: FC<Props> = ({
{
!hideSearch && (
<>
<div className={cn('mx-2 mb-1 mt-2', searchBoxClassName)} onClick={e => e.stopPropagation()}>
<div className={cn('var-search-input-wrapper mx-2 mb-1 mt-2', searchBoxClassName)} onClick={e => e.stopPropagation()}>
<Input
className='var-search-input'
showLeftIcon
showClearIcon
value={searchText}
placeholder={t('workflow.common.searchVar') || ''}
onChange={e => setSearchText(e.target.value)}
onKeyDown={handleKeyDown}
onClear={() => setSearchText('')}
onBlur={onBlur}
autoFocus
/>
</div>

Chargement…
Annuler
Enregistrer