123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- 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(0), // fix hide cursor
- shift(),
- flip(),
- ],
- })
- const [editor] = useLexicalComposerContext()
- const checkForTriggerMatch = useBasicTypeaheadTriggerMatch(triggerString, {
- minLength: 0,
- maxLength: 0,
- })
- const [queryString, setQueryString] = useState<string | null>(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<MenuRenderFn<PromptOption | VariableOption>>((
- anchorElementRef,
- { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },
- ) => {
- if (anchorElementRef.current && (allOptions.length || workflowVariableBlock?.show)) {
- return (
- <>
- {
- ReactDOM.createPortal(
- <div ref={refs.setReference}></div>,
- anchorElementRef.current,
- )
- }
- {
- elements.reference && (
- <FloatingPortal id='typeahead-menu'>
- <div
- className='w-[260px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg overflow-y-auto'
- style={{
- ...floatingStyles,
- maxHeight: 'calc(1 / 3 * 100vh)',
- }}
- ref={refs.setFloating}
- >
- {
- !!promptOptions.length && (
- <>
- <PromptMenu
- startIndex={0}
- selectedIndex={selectedIndex}
- options={promptOptions}
- onClick={(index, option) => {
- if (option.disabled)
- return
- setHighlightedIndex(index)
- selectOptionAndCleanUp(option)
- }}
- onMouseEnter={(index, option) => {
- if (option.disabled)
- return
- setHighlightedIndex(index)
- }}
- />
- </>
- )
- }
- {
- !!variableOptions.length && (
- <>
- {
- !!promptOptions.length && (
- <div className='h-[1px] bg-gray-100'></div>
- )
- }
- <VariableMenu
- startIndex={promptOptions.length}
- selectedIndex={selectedIndex}
- options={variableOptions}
- onClick={(index, option) => {
- 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) && (
- <div className='h-[1px] bg-gray-100'></div>
- )
- }
- <VariableMenu
- startIndex={promptOptions.length + variableOptions.length}
- selectedIndex={selectedIndex}
- options={externalToolOptions}
- onClick={(index, option) => {
- 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) && (
- <div className='h-[1px] bg-gray-100'></div>
- )
- }
- <div className='p-1'>
- <VarReferenceVars
- hideSearch
- vars={workflowVariableOptions}
- onChange={(variables: string[]) => {
- handleSelectWorkflowVariable(variables)
- }}
- />
- </div>
- </>
- )
- }
- </div>
- </FloatingPortal>
- )
- }
- </>
- )
- }
- return null
- }, [
- allOptions,
- promptOptions,
- variableOptions,
- externalToolOptions,
- queryString,
- workflowVariableBlock?.show,
- workflowVariableOptions,
- handleSelectWorkflowVariable,
- elements,
- floatingStyles,
- refs,
- ])
- return (
- <LexicalTypeaheadMenuPlugin
- options={allOptions as any}
- onQueryChange={setQueryString}
- onSelectOption={onSelectOption}
- anchorClassName='z-[999999]'
- menuRenderFn={renderMenu}
- triggerFn={checkForTriggerMatch}
- />
- )
- }
- export default memo(ComponentPicker)
|