import { memo, useCallback, useState, } from 'react' import ReactDOM from 'react-dom' import { FloatingPortal, flip, offset, shift, useFloating, } from '@floating-ui/react' import type { TextNode } from 'lexical' import type { MenuRenderFn } from '@lexical/react/LexicalTypeaheadMenuPlugin' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { LexicalTypeaheadMenuPlugin } from '@lexical/react/LexicalTypeaheadMenuPlugin' import type { ContextBlockType, ExternalToolBlockType, HistoryBlockType, QueryBlockType, VariableBlockType, WorkflowVariableBlockType, } from '../../types' import { useBasicTypeaheadTriggerMatch } from '../../hooks' import { INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND } from '../workflow-variable-block' import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '../variable-block' import { $splitNodeContainingQuery } from '../../utils' import type { PromptOption } from './prompt-option' import PromptMenu from './prompt-menu' import VariableMenu from './variable-menu' import type { VariableOption } from './variable-option' import { useOptions } from './hooks' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' import { useEventEmitterContextContext } from '@/context/event-emitter' type ComponentPickerProps = { triggerString: string contextBlock?: ContextBlockType queryBlock?: QueryBlockType historyBlock?: HistoryBlockType variableBlock?: VariableBlockType externalToolBlock?: ExternalToolBlockType workflowVariableBlock?: WorkflowVariableBlockType } const ComponentPicker = ({ triggerString, contextBlock, queryBlock, historyBlock, variableBlock, externalToolBlock, workflowVariableBlock, }: ComponentPickerProps) => { const { eventEmitter } = useEventEmitterContextContext() const { refs, floatingStyles, elements } = useFloating({ placement: 'bottom-start', middleware: [ offset(16), // fix hide cursor shift(), flip(), ], }) const [editor] = useLexicalComposerContext() const checkForTriggerMatch = useBasicTypeaheadTriggerMatch(triggerString, { minLength: 0, maxLength: 0, }) const [queryString, setQueryString] = useState(null) eventEmitter?.useSubscription((v: any) => { if (v.type === INSERT_VARIABLE_VALUE_BLOCK_COMMAND) editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${v.payload}}}`) }) const { allOptions, promptOptions, variableOptions, externalToolOptions, workflowVariableOptions, } = useOptions( contextBlock, queryBlock, historyBlock, variableBlock, externalToolBlock, workflowVariableBlock, ) const onSelectOption = useCallback( ( selectedOption: PromptOption | VariableOption, nodeToRemove: TextNode | null, closeMenu: () => void, matchingString: string, ) => { editor.update(() => { if (nodeToRemove && selectedOption?.key) nodeToRemove.remove() if (selectedOption?.onSelect) selectedOption.onSelect(matchingString) closeMenu() }) }, [editor], ) const handleSelectWorkflowVariable = useCallback((variables: string[]) => { editor.update(() => { const needRemove = $splitNodeContainingQuery(checkForTriggerMatch(triggerString, editor)!) if (needRemove) needRemove.remove() }) if (variables[1] === 'sys.query' || variables[1] === 'sys.files') editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]]) else editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables) }, [editor, checkForTriggerMatch, triggerString]) const renderMenu = useCallback>(( anchorElementRef, { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, ) => { if (anchorElementRef.current && (allOptions.length || workflowVariableBlock?.show)) { return ( <> { ReactDOM.createPortal(
, anchorElementRef.current, ) } { elements.reference && (
{ !!promptOptions.length && ( <> { if (option.disabled) return setHighlightedIndex(index) selectOptionAndCleanUp(option) }} onMouseEnter={(index, option) => { if (option.disabled) return setHighlightedIndex(index) }} /> ) } { !!variableOptions.length && ( <> { !!promptOptions.length && (
) } { if (option.disabled) return setHighlightedIndex(index) selectOptionAndCleanUp(option) }} onMouseEnter={(index, option) => { if (option.disabled) return setHighlightedIndex(index) }} queryString={queryString} /> ) } { !!externalToolOptions.length && ( <> { (!!promptOptions.length || !!variableOptions.length) && (
) } { if (option.disabled) return setHighlightedIndex(index) selectOptionAndCleanUp(option) }} onMouseEnter={(index, option) => { if (option.disabled) return setHighlightedIndex(index) }} queryString={queryString} /> ) } { workflowVariableBlock?.show && ( <> { (!!promptOptions.length || !!variableOptions.length || !!externalToolOptions.length) && (
) }
{ handleSelectWorkflowVariable(variables) }} />
) }
) } ) } return null }, [ allOptions, promptOptions, variableOptions, externalToolOptions, queryString, workflowVariableBlock?.show, workflowVariableOptions, handleSelectWorkflowVariable, elements, floatingStyles, refs, ]) return ( ) } export default memo(ComponentPicker)